From f90d9f53beb844265c4153159aadb0baf81bafdb Mon Sep 17 00:00:00 2001
From: liyong <18434998025@163.com>
Date: 星期四, 30 七月 2026 18:07:11 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_business' into dev_business
---
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java | 7
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/permission/AfterSalePermissionMapper.java | 48
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java | 15
yudao-server/pom.xml | 7
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java | 78
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryImportExcelVO.java | 9
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java | 3
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java | 24
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleRepairStatusEnum.java | 32
docs/machinery_new_fields_frontend.md | 140 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryPageReqVO.java | 3
.playwright-mcp/page-2026-07-29T06-47-38-983Z.yml | 113 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java | 15
yudao-module-aftersales/pom.xml | 118 +
docs/sql/aftersales_init.sql | 170 +
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApi.java | 20
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/MesDvMachineryController.java | 8
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java | 3
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java | 5
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/documentaudit/vo/MesPdDocumentAuditPageReqVO.java | 3
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/AfterSaleReturnController.java | 95
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java | 28
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java | 4
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java | 3
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java | 18
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java | 1
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnServiceImpl.java | 221 ++
.playwright-mcp/page-2026-07-29T06-56-04-077Z.yml | 1
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/permission/AfterSalePermissionDO.java | 55
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java | 16
.playwright-mcp/page-2026-07-29T07-14-58-476Z.yml | 1
docs/aftersales_frontend_integration.md | 636 +++++
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketPageReqVO.java | 39
.playwright-mcp/page-2026-07-29T07-10-45-745Z.yml | 1
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSalePermissionUtils.java | 94
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/documentaudit/MesPdDocumentAuditServiceImpl.java | 15
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnMapper.java | 48
sql/mysql/mes_pd_menu_init.sql | 37
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnService.java | 92
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java | 8
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java | 165 +
.playwright-mcp/page-2026-07-29T06-42-11-724Z.yml | 1
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java | 6
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java | 32
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java | 4
yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleTicketRespDTO.java | 31
pom.xml | 3
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java | 89
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/repair/AfterSaleRepairMapper.java | 34
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApi.java | 21
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java | 3
.playwright-mcp/page-2026-07-29T06-47-24-223Z.yml | 1
.mcp.json.tmp | 8
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketDO.java | 116 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesSaveReqVO.java | 7
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesRespVO.java | 6
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java | 32
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairServiceImpl.java | 110 +
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApiImpl.java | 59
docs/pro_task_calculate_duration.md | 81
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/RedisKeyConstants.java | 16
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java | 9
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnSaveReqVO.java | 88
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java | 9
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/bo/AfterSalePermissionCreateReqBO.java | 38
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSaleSceneTypeEnum.java | 54
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/no/AfterSaleNoRedisDAO.java | 46
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java | 38
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionService.java | 61
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApiImpl.java | 28
.playwright-mcp/page-2026-07-29T06-47-57-125Z.yml | 152 +
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java | 18
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java | 10
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java | 4
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionServiceImpl.java | 64
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java | 38
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java | 151 +
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnDO.java | 112 +
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/dto/MesWmReturnSalesCreateReqDTO.java | 44
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApiImpl.java | 27
.playwright-mcp/page-2026-07-29T06-40-35-404Z.yml | 1
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnPageReqVO.java | 33
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java | 9
.playwright-mcp/page-2026-07-29T06-42-36-804Z.yml | 112 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/documentaudit/MesPdDocumentAuditMapper.java | 10
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/LogRecordConstants.java | 48
docs/document_audit_project_filter_fix.md | 59
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java | 1
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketService.java | 113 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesLineMapper.java | 22
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java | 5
yudao-module-aftersales-api/pom.xml | 33
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketItemMapper.java | 26
.playwright-mcp/page-2026-07-29T07-14-24-445Z.yml | 1
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleBizTypeEnum.java | 40
.playwright-mcp/page-2026-07-29T06-56-14-335Z.yml | 122 +
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java | 15
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSalePermissionLevelEnum.java | 58
docs/pro_archive_integration.md | 154 +
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleReturnStatusEnum.java | 37
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/machinery/MesDvMachineryMapper.java | 1
yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApi.java | 54
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java | 5
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketMapper.java | 58
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/repair/AfterSaleRepairDO.java | 78
.playwright-mcp/page-2026-07-29T06-43-35-225Z.yml | 112 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java | 4
yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleReturnRespDTO.java | 32
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnRespVO.java | 139 +
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java | 1
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java | 4
docs/crm_customer_view_all.md | 88
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnItemDO.java | 78
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java | 1
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnsales/MesWmReturnSalesDO.java | 8
.playwright-mcp/page-2026-07-29T07-14-44-587Z.yml | 121 +
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnItemMapper.java | 26
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairService.java | 63
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesPageReqVO.java | 11
.mcp.json | 2
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java | 5
.playwright-mcp/page-2026-07-29T07-12-31-548Z.yml | 121 +
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleTicketStatusEnum.java | 35
.playwright-mcp/page-2026-07-29T06-51-00-932Z.yml | 152 +
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java | 212 +
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java | 14
126 files changed, 6,341 insertions(+), 33 deletions(-)
diff --git a/.mcp.json b/.mcp.json
index b2068ac..19d9d48 100644
--- a/.mcp.json
+++ b/.mcp.json
@@ -1,7 +1,7 @@
{
"mcpServers": {
"idea": {
- "url": "http://127.0.0.1:64342/stream",
+ "url": "http://127.0.0.1:64417/stream",
"type": "http"
}
}
diff --git a/.mcp.json.tmp b/.mcp.json.tmp
new file mode 100644
index 0000000..19d9d48
--- /dev/null
+++ b/.mcp.json.tmp
@@ -0,0 +1,8 @@
+{
+ "mcpServers": {
+ "idea": {
+ "url": "http://127.0.0.1:64417/stream",
+ "type": "http"
+ }
+ }
+}
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-40-35-404Z.yml b/.playwright-mcp/page-2026-07-29T06-40-35-404Z.yml
new file mode 100644
index 0000000..987bb5e
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-40-35-404Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-42-11-724Z.yml b/.playwright-mcp/page-2026-07-29T06-42-11-724Z.yml
new file mode 100644
index 0000000..987bb5e
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-42-11-724Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-42-36-804Z.yml b/.playwright-mcp/page-2026-07-29T06-42-36-804Z.yml
new file mode 100644
index 0000000..b4e4c37
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-42-36-804Z.yml
@@ -0,0 +1,112 @@
+- generic [ref=e7]:
+ - complementary [ref=e8]:
+ - generic [ref=e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=e18]
+ - generic [ref=e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=e22]:
+ - listitem [ref=e23]:
+ - generic [ref=e24] [cursor=pointer]: 姒傝
+ - listitem [ref=e34]:
+ - generic [ref=e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=e43]:
+ - generic [ref=e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=e50]:
+ - generic [ref=e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=e57]:
+ - generic [ref=e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=e65]:
+ - generic [ref=e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=e74]:
+ - generic [ref=e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=e82]:
+ - generic [ref=e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=e89]:
+ - generic [ref=e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=e96]:
+ - generic [ref=e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=e103]:
+ - generic [ref=e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=e110]:
+ - generic [ref=e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=e117]:
+ - generic [ref=e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=e131]:
+ - generic [ref=e132]:
+ - banner [ref=e133]:
+ - button [ref=e134] [cursor=pointer]
+ - button [ref=e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=e142]:
+ - list [ref=e143]:
+ - listitem [ref=e144]:
+ - link [ref=e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=e152]
+ - listitem [ref=e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=e156]
+ - generic [ref=e159]:
+ - generic [ref=e161] [cursor=pointer]:
+ - generic [ref=e165]: 鎼滅储
+ - generic [ref=e166]: Ctrl K
+ - button [ref=e168] [cursor=pointer]
+ - button "dark" [ref=e173] [cursor=pointer]
+ - button [ref=e182] [cursor=pointer]
+ - button [ref=e188] [cursor=pointer]
+ - button [ref=e191] [cursor=pointer]
+ - button [ref=e197] [cursor=pointer]:
+ - button [ref=e199]
+ - button "宸ヤ綔鍙�" [ref=e203] [cursor=pointer]
+ - button "婧愮爜" [ref=e207] [cursor=pointer]
+ - generic [ref=e214]:
+ - generic [ref=e219]:
+ - generic [ref=e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=e254] [cursor=pointer]
+ - main [ref=e272]:
+ - generic [ref=e275]:
+ - generic [ref=e276]:
+ - button [ref=e277] [cursor=pointer]
+ - generic [ref=e280]: 娴嬭瘯椤圭洰
+ - generic [ref=e281]: 娴嬭瘯缂栧彿
+ - generic [ref=e282]: 璇勬爣涓�
+ - generic [ref=e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=e284] [cursor=pointer]
+ - generic [ref=e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=e291]:
+ - generic [ref=e292]:
+ - generic [ref=e293]: 棰勭畻閲戦
+ - generic [ref=e294]: 15,000 鍏�
+ - generic [ref=e295]:
+ - generic [ref=e296]: 璐熻矗浜�
+ - generic [ref=e297]: "-"
+ - generic [ref=e298]:
+ - generic [ref=e299]: 閲囪喘缁勭粐
+ - generic [ref=e300]: "-"
+ - generic [ref=e301]:
+ - generic [ref=e302]: 寮�鏍囨椂闂�
+ - generic [ref=e303]: 2026-07-29 11:20:11
+ - generic [ref=e304]:
+ - generic [ref=e305]: 寮�濮嬫椂闂�
+ - generic [ref=e306]: 2026-07-29 11:13:02
+ - generic [ref=e307]:
+ - generic [ref=e308]: 鎴鏃堕棿
+ - generic [ref=e309]: 2026-07-29 19:13:06
+ - generic [ref=e313]:
+ - tablist [ref=e314]:
+ - generic [ref=e316]:
+ - tab [ref=e318] [cursor=pointer]
+ - tab [ref=e323] [cursor=pointer]
+ - tab [ref=e328] [cursor=pointer]
+ - tab [ref=e333] [cursor=pointer]
+ - tab [ref=e338] [cursor=pointer]
+ - tab [active] [selected] [ref=e343] [cursor=pointer]
+ - tabpanel "瀹氭爣" [ref=e404]:
+ - generic [ref=e405]:
+ - generic "灏氭湭瀹氭爣"
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-43-35-225Z.yml b/.playwright-mcp/page-2026-07-29T06-43-35-225Z.yml
new file mode 100644
index 0000000..0dd6375
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-43-35-225Z.yml
@@ -0,0 +1,112 @@
+- generic [ref=e7]:
+ - complementary [ref=e8]:
+ - generic [ref=e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=e18]
+ - generic [ref=e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=e22]:
+ - listitem [ref=e23]:
+ - generic [ref=e24] [cursor=pointer]: 姒傝
+ - listitem [ref=e34]:
+ - generic [ref=e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=e43]:
+ - generic [ref=e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=e50]:
+ - generic [ref=e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=e57]:
+ - generic [ref=e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=e65]:
+ - generic [ref=e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=e74]:
+ - generic [ref=e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=e82]:
+ - generic [ref=e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=e89]:
+ - generic [ref=e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=e96]:
+ - generic [ref=e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=e103]:
+ - generic [ref=e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=e110]:
+ - generic [ref=e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=e117]:
+ - generic [ref=e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=e131]:
+ - generic [ref=e132]:
+ - banner [ref=e133]:
+ - button [ref=e134] [cursor=pointer]
+ - button [ref=e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=e142]:
+ - list [ref=e143]:
+ - listitem [ref=e144]:
+ - link [ref=e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=e152]
+ - listitem [ref=e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=e156]
+ - generic [ref=e159]:
+ - generic [ref=e161] [cursor=pointer]:
+ - generic [ref=e165]: 鎼滅储
+ - generic [ref=e166]: Ctrl K
+ - button [ref=e168] [cursor=pointer]
+ - button "dark" [ref=e173] [cursor=pointer]
+ - button [ref=e182] [cursor=pointer]
+ - button [ref=e188] [cursor=pointer]
+ - button [ref=e191] [cursor=pointer]
+ - button [ref=e197] [cursor=pointer]:
+ - button [ref=e199]
+ - button "宸ヤ綔鍙�" [ref=e203] [cursor=pointer]
+ - button "婧愮爜" [ref=e207] [cursor=pointer]
+ - generic [ref=e214]:
+ - generic [ref=e219]:
+ - generic [ref=e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=e254] [cursor=pointer]
+ - main [ref=e272]:
+ - generic [ref=e275]:
+ - generic [ref=e276]:
+ - button [ref=e277] [cursor=pointer]
+ - generic [ref=e280]: 娴嬭瘯椤圭洰
+ - generic [ref=e281]: 娴嬭瘯缂栧彿
+ - generic [ref=e282]: 璇勬爣涓�
+ - generic [ref=e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=e284] [cursor=pointer]
+ - generic [ref=e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=e291]:
+ - generic [ref=e292]:
+ - generic [ref=e293]: 棰勭畻閲戦
+ - generic [ref=e294]: 15,000 鍏�
+ - generic [ref=e295]:
+ - generic [ref=e296]: 璐熻矗浜�
+ - generic [ref=e297]: "-"
+ - generic [ref=e298]:
+ - generic [ref=e299]: 閲囪喘缁勭粐
+ - generic [ref=e300]: "-"
+ - generic [ref=e301]:
+ - generic [ref=e302]: 寮�鏍囨椂闂�
+ - generic [ref=e303]: 2026-07-29 11:20:11
+ - generic [ref=e304]:
+ - generic [ref=e305]: 寮�濮嬫椂闂�
+ - generic [ref=e306]: 2026-07-29 11:13:02
+ - generic [ref=e307]:
+ - generic [ref=e308]: 鎴鏃堕棿
+ - generic [ref=e309]: 2026-07-29 19:13:06
+ - generic [ref=e313]:
+ - tablist [ref=e314]:
+ - generic [ref=e316]:
+ - tab [ref=e318] [cursor=pointer]
+ - tab [ref=e323] [cursor=pointer]
+ - tab [ref=e328] [cursor=pointer]
+ - tab [active] [selected] [ref=e333] [cursor=pointer]
+ - tab [ref=e338] [cursor=pointer]
+ - tab [ref=e343] [cursor=pointer]
+ - tabpanel "寮�鏍�" [ref=e406]:
+ - generic [ref=e407]:
+ - generic "灏氭湭寮�鏍�"
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-47-24-223Z.yml b/.playwright-mcp/page-2026-07-29T06-47-24-223Z.yml
new file mode 100644
index 0000000..987bb5e
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-47-24-223Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-47-38-983Z.yml b/.playwright-mcp/page-2026-07-29T06-47-38-983Z.yml
new file mode 100644
index 0000000..75b64e4
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-47-38-983Z.yml
@@ -0,0 +1,113 @@
+- generic [ref=f1e7]:
+ - complementary [ref=f1e8]:
+ - generic [ref=f1e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f1e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f1e18]
+ - generic [ref=f1e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=f1e22]:
+ - listitem [ref=f1e23]:
+ - generic [ref=f1e24] [cursor=pointer]: 姒傝
+ - listitem [ref=f1e34]:
+ - generic [ref=f1e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=f1e43]:
+ - generic [ref=f1e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=f1e50]:
+ - generic [ref=f1e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=f1e57]:
+ - generic [ref=f1e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=f1e65]:
+ - generic [ref=f1e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=f1e74]:
+ - generic [ref=f1e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=f1e82]:
+ - generic [ref=f1e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=f1e89]:
+ - generic [ref=f1e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=f1e96]:
+ - generic [ref=f1e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=f1e103]:
+ - generic [ref=f1e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=f1e110]:
+ - generic [ref=f1e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=f1e117]:
+ - generic [ref=f1e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=f1e131]:
+ - generic [ref=f1e132]:
+ - banner [ref=f1e133]:
+ - button [ref=f1e134] [cursor=pointer]
+ - button [ref=f1e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=f1e142]:
+ - list [ref=f1e143]:
+ - listitem [ref=f1e144]:
+ - link [ref=f1e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=f1e152]
+ - listitem [ref=f1e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=f1e156]
+ - generic [ref=f1e159]:
+ - generic [ref=f1e161] [cursor=pointer]:
+ - generic [ref=f1e165]: 鎼滅储
+ - generic [ref=f1e166]: Ctrl K
+ - button [ref=f1e168] [cursor=pointer]
+ - button "dark" [ref=f1e173] [cursor=pointer]
+ - button [ref=f1e182] [cursor=pointer]
+ - button [ref=f1e188] [cursor=pointer]
+ - button [ref=f1e191] [cursor=pointer]
+ - button [ref=f1e197] [cursor=pointer]:
+ - button [ref=f1e199]
+ - button "宸ヤ綔鍙�" [ref=f1e203] [cursor=pointer]
+ - button "婧愮爜" [ref=f1e207] [cursor=pointer]
+ - generic [ref=f1e214]:
+ - generic [ref=f1e219]:
+ - generic [ref=f1e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=f1e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=f1e254] [cursor=pointer]
+ - main [ref=f1e272]:
+ - generic [ref=f1e275]:
+ - generic [ref=f1e276]:
+ - button [ref=f1e277] [cursor=pointer]
+ - generic [ref=f1e280]: 娴嬭瘯椤圭洰
+ - generic [ref=f1e281]: 娴嬭瘯缂栧彿
+ - generic [ref=f1e282]: 璇勬爣涓�
+ - generic [ref=f1e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=f1e284] [cursor=pointer]
+ - generic [ref=f1e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=f1e291]:
+ - generic [ref=f1e292]:
+ - generic [ref=f1e293]: 棰勭畻閲戦
+ - generic [ref=f1e294]: 15,000 鍏�
+ - generic [ref=f1e295]:
+ - generic [ref=f1e296]: 璐熻矗浜�
+ - generic [ref=f1e297]: "-"
+ - generic [ref=f1e298]:
+ - generic [ref=f1e299]: 閲囪喘缁勭粐
+ - generic [ref=f1e300]: "-"
+ - generic [ref=f1e301]:
+ - generic [ref=f1e302]: 寮�鏍囨椂闂�
+ - generic [ref=f1e303]: 2026-07-29 11:20:11
+ - generic [ref=f1e304]:
+ - generic [ref=f1e305]: 寮�濮嬫椂闂�
+ - generic [ref=f1e306]: 2026-07-29 11:13:02
+ - generic [ref=f1e307]:
+ - generic [ref=f1e308]: 鎴鏃堕棿
+ - generic [ref=f1e309]: 2026-07-29 19:13:06
+ - generic [ref=f1e313]:
+ - tablist [ref=f1e314]:
+ - generic [ref=f1e316]:
+ - tab [ref=f1e318] [cursor=pointer]
+ - tab [ref=f1e323] [cursor=pointer]
+ - tab [ref=f1e328] [cursor=pointer]
+ - tab [ref=f1e333] [cursor=pointer]
+ - tab [ref=f1e338] [cursor=pointer]
+ - tab [active] [selected] [ref=f1e343] [cursor=pointer]
+ - tabpanel "瀹氭爣" [ref=f1e404]:
+ - generic [ref=f1e405]:
+ - generic "灏氭湭瀹氭爣"
+ - button "鍒涘缓瀹氭爣" [ref=f1e406] [cursor=pointer]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-47-57-125Z.yml b/.playwright-mcp/page-2026-07-29T06-47-57-125Z.yml
new file mode 100644
index 0000000..3bbcfe5
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-47-57-125Z.yml
@@ -0,0 +1,152 @@
+- generic [ref=f1e4]:
+ - generic [ref=f1e7]:
+ - complementary [ref=f1e8]:
+ - generic [ref=f1e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f1e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f1e18]
+ - generic [ref=f1e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=f1e22]:
+ - listitem [ref=f1e23]:
+ - generic [ref=f1e24] [cursor=pointer]: 姒傝
+ - listitem [ref=f1e34]:
+ - generic [ref=f1e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=f1e43]:
+ - generic [ref=f1e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=f1e50]:
+ - generic [ref=f1e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=f1e57]:
+ - generic [ref=f1e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=f1e65]:
+ - generic [ref=f1e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=f1e74]:
+ - generic [ref=f1e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=f1e82]:
+ - generic [ref=f1e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=f1e89]:
+ - generic [ref=f1e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=f1e96]:
+ - generic [ref=f1e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=f1e103]:
+ - generic [ref=f1e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=f1e110]:
+ - generic [ref=f1e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=f1e117]:
+ - generic [ref=f1e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=f1e131]:
+ - generic [ref=f1e132]:
+ - banner [ref=f1e133]:
+ - button [ref=f1e134] [cursor=pointer]
+ - button [ref=f1e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=f1e142]:
+ - list [ref=f1e143]:
+ - listitem [ref=f1e144]:
+ - link [ref=f1e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=f1e152]
+ - listitem [ref=f1e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=f1e156]
+ - generic [ref=f1e159]:
+ - generic [ref=f1e161] [cursor=pointer]:
+ - generic [ref=f1e165]: 鎼滅储
+ - generic [ref=f1e166]: Ctrl K
+ - button [ref=f1e168] [cursor=pointer]
+ - button "dark" [ref=f1e173] [cursor=pointer]
+ - button [ref=f1e182] [cursor=pointer]
+ - button [ref=f1e188] [cursor=pointer]
+ - button [ref=f1e191] [cursor=pointer]
+ - button [ref=f1e197] [cursor=pointer]:
+ - button [ref=f1e199]
+ - button "宸ヤ綔鍙�" [ref=f1e203] [cursor=pointer]
+ - button "婧愮爜" [ref=f1e207] [cursor=pointer]
+ - generic [ref=f1e214]:
+ - generic [ref=f1e219]:
+ - generic [ref=f1e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=f1e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=f1e254] [cursor=pointer]
+ - main [ref=f1e272]:
+ - generic [ref=f1e275]:
+ - generic [ref=f1e276]:
+ - button [ref=f1e277] [cursor=pointer]
+ - generic [ref=f1e280]: 娴嬭瘯椤圭洰
+ - generic [ref=f1e281]: 娴嬭瘯缂栧彿
+ - generic [ref=f1e282]: 璇勬爣涓�
+ - generic [ref=f1e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=f1e284] [cursor=pointer]
+ - generic [ref=f1e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=f1e291]:
+ - generic [ref=f1e292]:
+ - generic [ref=f1e293]: 棰勭畻閲戦
+ - generic [ref=f1e294]: 15,000 鍏�
+ - generic [ref=f1e295]:
+ - generic [ref=f1e296]: 璐熻矗浜�
+ - generic [ref=f1e297]: "-"
+ - generic [ref=f1e298]:
+ - generic [ref=f1e299]: 閲囪喘缁勭粐
+ - generic [ref=f1e300]: "-"
+ - generic [ref=f1e301]:
+ - generic [ref=f1e302]: 寮�鏍囨椂闂�
+ - generic [ref=f1e303]: 2026-07-29 11:20:11
+ - generic [ref=f1e304]:
+ - generic [ref=f1e305]: 寮�濮嬫椂闂�
+ - generic [ref=f1e306]: 2026-07-29 11:13:02
+ - generic [ref=f1e307]:
+ - generic [ref=f1e308]: 鎴鏃堕棿
+ - generic [ref=f1e309]: 2026-07-29 19:13:06
+ - generic [ref=f1e313]:
+ - tablist [ref=f1e314]:
+ - generic [ref=f1e316]:
+ - tab [ref=f1e318] [cursor=pointer]
+ - tab [ref=f1e323] [cursor=pointer]
+ - tab [ref=f1e328] [cursor=pointer]
+ - tab [ref=f1e333] [cursor=pointer]
+ - tab [ref=f1e338] [cursor=pointer]
+ - tab [selected] [ref=f1e343] [cursor=pointer]
+ - tabpanel "瀹氭爣" [ref=f1e404]:
+ - generic [ref=f1e405]:
+ - generic "灏氭湭瀹氭爣"
+ - button "鍒涘缓瀹氭爣" [active] [ref=f1e406] [cursor=pointer]
+ - dialog "鍒涘缓瀹氭爣" [ref=f1e412]:
+ - document:
+ - generic [ref=f1e413]:
+ - button "Close" [ref=f1e414] [cursor=pointer]:
+ - img "close" [ref=f1e416]
+ - generic [ref=f1e419]: 鍒涘缓瀹氭爣
+ - generic [ref=f1e422]:
+ - generic [ref=f1e424]:
+ - generic "瀹氭爣缂栧彿" [ref=f1e426]: "* 瀹氭爣缂栧彿"
+ - textbox "濡� AWARD2024010001" [ref=f1e430]
+ - generic [ref=f1e431]:
+ - generic [ref=f1e434]:
+ - generic "渚涘簲鍟�" [ref=f1e436]: "* 渚涘簲鍟�"
+ - generic [ref=f1e441]:
+ - combobox [ref=f1e443]
+ - generic "0" [ref=f1e444]
+ - generic [ref=f1e447]:
+ - generic "鎶曟爣" [ref=f1e449]: "* 鎶曟爣"
+ - generic [ref=f1e454]:
+ - combobox [ref=f1e456]
+ - generic "0" [ref=f1e457]
+ - generic [ref=f1e459]:
+ - generic "瀹氭爣閲戦" [ref=f1e461]: "* 瀹氭爣閲戦"
+ - generic [ref=f1e466]:
+ - generic [ref=f1e467]:
+ - generic [ref=f1e468]:
+ - button "Increase Value" [ref=f1e469] [cursor=pointer]:
+ - img "up" [ref=f1e470]
+ - button "Decrease Value" [ref=f1e473] [cursor=pointer]:
+ - img "down" [ref=f1e474]
+ - spinbutton "瀹氭爣閲戦" [ref=f1e478]
+ - generic [ref=f1e479]: 鍏�
+ - generic [ref=f1e481]:
+ - generic "澶囨敞" [ref=f1e483]
+ - textbox "濡傦細缁煎悎璇勬爣绗竴鍚�" [ref=f1e487]
+ - generic [ref=f1e488]:
+ - button "鍙� 娑�" [ref=f1e489] [cursor=pointer]
+ - button "纭� 瀹�" [ref=f1e491] [cursor=pointer]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-51-00-932Z.yml b/.playwright-mcp/page-2026-07-29T06-51-00-932Z.yml
new file mode 100644
index 0000000..f4d9f7c
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-51-00-932Z.yml
@@ -0,0 +1,152 @@
+- generic [ref=f1e4]:
+ - generic [ref=f1e7]:
+ - complementary [ref=f1e8]:
+ - generic [ref=f1e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f1e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f1e18]
+ - generic [ref=f1e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=f1e22]:
+ - listitem [ref=f1e23]:
+ - generic [ref=f1e24] [cursor=pointer]: 姒傝
+ - listitem [ref=f1e34]:
+ - generic [ref=f1e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=f1e43]:
+ - generic [ref=f1e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=f1e50]:
+ - generic [ref=f1e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=f1e57]:
+ - generic [ref=f1e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=f1e65]:
+ - generic [ref=f1e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=f1e74]:
+ - generic [ref=f1e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=f1e82]:
+ - generic [ref=f1e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=f1e89]:
+ - generic [ref=f1e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=f1e96]:
+ - generic [ref=f1e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=f1e103]:
+ - generic [ref=f1e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=f1e110]:
+ - generic [ref=f1e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=f1e117]:
+ - generic [ref=f1e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=f1e131]:
+ - generic [ref=f1e132]:
+ - banner [ref=f1e133]:
+ - button [ref=f1e134] [cursor=pointer]
+ - button [ref=f1e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=f1e142]:
+ - list [ref=f1e143]:
+ - listitem [ref=f1e144]:
+ - link [ref=f1e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=f1e152]
+ - listitem [ref=f1e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=f1e156]
+ - generic [ref=f1e159]:
+ - generic [ref=f1e161] [cursor=pointer]:
+ - generic [ref=f1e165]: 鎼滅储
+ - generic [ref=f1e166]: Ctrl K
+ - button [ref=f1e168] [cursor=pointer]
+ - button "dark" [ref=f1e173] [cursor=pointer]
+ - button [ref=f1e182] [cursor=pointer]
+ - button [ref=f1e188] [cursor=pointer]
+ - button [ref=f1e191] [cursor=pointer]
+ - button [ref=f1e197] [cursor=pointer]:
+ - button [ref=f1e199]
+ - button "宸ヤ綔鍙�" [ref=f1e203] [cursor=pointer]
+ - button "婧愮爜" [ref=f1e207] [cursor=pointer]
+ - generic [ref=f1e214]:
+ - generic [ref=f1e219]:
+ - generic [ref=f1e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=f1e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=f1e254] [cursor=pointer]
+ - main [ref=f1e272]:
+ - generic [ref=f1e275]:
+ - generic [ref=f1e276]:
+ - button [ref=f1e277] [cursor=pointer]
+ - generic [ref=f1e280]: 娴嬭瘯椤圭洰
+ - generic [ref=f1e281]: 娴嬭瘯缂栧彿
+ - generic [ref=f1e282]: 璇勬爣涓�
+ - generic [ref=f1e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=f1e284] [cursor=pointer]
+ - generic [ref=f1e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=f1e291]:
+ - generic [ref=f1e292]:
+ - generic [ref=f1e293]: 棰勭畻閲戦
+ - generic [ref=f1e294]: 15,000 鍏�
+ - generic [ref=f1e295]:
+ - generic [ref=f1e296]: 璐熻矗浜�
+ - generic [ref=f1e297]: "-"
+ - generic [ref=f1e298]:
+ - generic [ref=f1e299]: 閲囪喘缁勭粐
+ - generic [ref=f1e300]: "-"
+ - generic [ref=f1e301]:
+ - generic [ref=f1e302]: 寮�鏍囨椂闂�
+ - generic [ref=f1e303]: 2026-07-29 11:20:11
+ - generic [ref=f1e304]:
+ - generic [ref=f1e305]: 寮�濮嬫椂闂�
+ - generic [ref=f1e306]: 2026-07-29 11:13:02
+ - generic [ref=f1e307]:
+ - generic [ref=f1e308]: 鎴鏃堕棿
+ - generic [ref=f1e309]: 2026-07-29 19:13:06
+ - generic [ref=f1e313]:
+ - tablist [ref=f1e314]:
+ - generic [ref=f1e316]:
+ - tab [ref=f1e318] [cursor=pointer]
+ - tab [ref=f1e323] [cursor=pointer]
+ - tab [ref=f1e328] [cursor=pointer]
+ - tab [ref=f1e333] [cursor=pointer]
+ - tab [ref=f1e338] [cursor=pointer]
+ - tab [selected] [ref=f1e343] [cursor=pointer]
+ - tabpanel "瀹氭爣" [ref=f1e404]:
+ - generic [ref=f1e405]:
+ - generic "灏氭湭瀹氭爣"
+ - button "鍒涘缓瀹氭爣" [ref=f1e406] [cursor=pointer]
+ - dialog "鍒涘缓瀹氭爣" [ref=f1e412]:
+ - document:
+ - generic [ref=f1e413]:
+ - button "Close" [ref=f1e414] [cursor=pointer]:
+ - img "close" [ref=f1e416]
+ - generic [ref=f1e419]: 鍒涘缓瀹氭爣
+ - generic [ref=f1e422]:
+ - generic [ref=f1e424]:
+ - generic "瀹氭爣缂栧彿" [ref=f1e426]: "* 瀹氭爣缂栧彿"
+ - textbox "濡� AWARD2024010001" [ref=f1e430]
+ - generic [ref=f1e431]:
+ - generic [ref=f1e434]:
+ - generic "渚涘簲鍟�" [ref=f1e436]: "* 渚涘簲鍟�"
+ - generic [ref=f1e441]:
+ - combobox [ref=f1e443]
+ - generic "0" [ref=f1e444]
+ - generic [ref=f1e447]:
+ - generic "鎶曟爣" [ref=f1e449]: "* 鎶曟爣"
+ - generic [ref=f1e454]:
+ - combobox [ref=f1e456]
+ - generic "0" [ref=f1e457]
+ - generic [ref=f1e459]:
+ - generic "瀹氭爣閲戦" [ref=f1e461]: "* 瀹氭爣閲戦"
+ - generic [ref=f1e466]:
+ - generic [ref=f1e467]:
+ - generic [ref=f1e468]:
+ - button "Increase Value" [ref=f1e469] [cursor=pointer]:
+ - img "up" [ref=f1e470]
+ - button "Decrease Value" [ref=f1e473] [cursor=pointer]:
+ - img "down" [ref=f1e474]
+ - spinbutton "瀹氭爣閲戦" [ref=f1e478]
+ - generic [ref=f1e479]: 鍏�
+ - generic [ref=f1e481]:
+ - generic "澶囨敞" [ref=f1e483]
+ - textbox "濡傦細缁煎悎璇勬爣绗竴鍚�" [ref=f1e487]
+ - generic [ref=f1e488]:
+ - button "鍙� 娑�" [ref=f1e489] [cursor=pointer]
+ - button "纭� 瀹�" [ref=f1e491] [cursor=pointer]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-56-04-077Z.yml b/.playwright-mcp/page-2026-07-29T06-56-04-077Z.yml
new file mode 100644
index 0000000..987bb5e
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-56-04-077Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T06-56-14-335Z.yml b/.playwright-mcp/page-2026-07-29T06-56-14-335Z.yml
new file mode 100644
index 0000000..090f728
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T06-56-14-335Z.yml
@@ -0,0 +1,122 @@
+- generic [ref=f2e7]:
+ - complementary [ref=f2e8]:
+ - generic [ref=f2e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f2e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f2e18]
+ - generic [ref=f2e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=f2e22]:
+ - listitem [ref=f2e23]:
+ - generic [ref=f2e24] [cursor=pointer]: 姒傝
+ - listitem [ref=f2e34]:
+ - generic [ref=f2e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=f2e43]:
+ - generic [ref=f2e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=f2e50]:
+ - generic [ref=f2e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=f2e57]:
+ - generic [ref=f2e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=f2e65]:
+ - generic [ref=f2e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=f2e74]:
+ - generic [ref=f2e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=f2e82]:
+ - generic [ref=f2e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=f2e89]:
+ - generic [ref=f2e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=f2e96]:
+ - generic [ref=f2e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=f2e103]:
+ - generic [ref=f2e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=f2e110]:
+ - generic [ref=f2e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=f2e117]:
+ - generic [ref=f2e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=f2e131]:
+ - generic [ref=f2e132]:
+ - banner [ref=f2e133]:
+ - button [ref=f2e134] [cursor=pointer]
+ - button [ref=f2e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=f2e142]:
+ - list [ref=f2e143]:
+ - listitem [ref=f2e144]:
+ - link [ref=f2e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=f2e152]
+ - listitem [ref=f2e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=f2e156]
+ - generic [ref=f2e159]:
+ - generic [ref=f2e161] [cursor=pointer]:
+ - generic [ref=f2e165]: 鎼滅储
+ - generic [ref=f2e166]: Ctrl K
+ - button [ref=f2e168] [cursor=pointer]
+ - button "dark" [ref=f2e173] [cursor=pointer]
+ - button [ref=f2e182] [cursor=pointer]
+ - button [ref=f2e188] [cursor=pointer]
+ - button [ref=f2e191] [cursor=pointer]
+ - button [ref=f2e197] [cursor=pointer]:
+ - button [ref=f2e199]
+ - button "宸ヤ綔鍙�" [ref=f2e203] [cursor=pointer]
+ - button "婧愮爜" [ref=f2e207] [cursor=pointer]
+ - generic [ref=f2e214]:
+ - generic [ref=f2e219]:
+ - generic [ref=f2e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=f2e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=f2e254] [cursor=pointer]
+ - main [ref=f2e272]:
+ - generic [ref=f2e275]:
+ - generic [ref=f2e276]:
+ - button [ref=f2e277] [cursor=pointer]
+ - generic [ref=f2e280]: 娴嬭瘯椤圭洰
+ - generic [ref=f2e281]: 娴嬭瘯缂栧彿
+ - generic [ref=f2e282]: 宸插畾鏍�
+ - generic [ref=f2e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=f2e284] [cursor=pointer]
+ - generic [ref=f2e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=f2e291]:
+ - generic [ref=f2e292]:
+ - generic [ref=f2e293]: 棰勭畻閲戦
+ - generic [ref=f2e294]: 15,000 鍏�
+ - generic [ref=f2e295]:
+ - generic [ref=f2e296]: 璐熻矗浜�
+ - generic [ref=f2e297]: "-"
+ - generic [ref=f2e298]:
+ - generic [ref=f2e299]: 閲囪喘缁勭粐
+ - generic [ref=f2e300]: "-"
+ - generic [ref=f2e301]:
+ - generic [ref=f2e302]: 寮�鏍囨椂闂�
+ - generic [ref=f2e303]: 2026-07-29 11:20:11
+ - generic [ref=f2e304]:
+ - generic [ref=f2e305]: 寮�濮嬫椂闂�
+ - generic [ref=f2e306]: 2026-07-29 11:13:02
+ - generic [ref=f2e307]:
+ - generic [ref=f2e308]: 鎴鏃堕棿
+ - generic [ref=f2e309]: 2026-07-29 19:13:06
+ - generic [ref=f2e313]:
+ - tablist [ref=f2e314]:
+ - generic [ref=f2e316]:
+ - tab [ref=f2e318] [cursor=pointer]
+ - tab [ref=f2e323] [cursor=pointer]
+ - tab [ref=f2e328] [cursor=pointer]
+ - tab [ref=f2e333] [cursor=pointer]
+ - tab [ref=f2e338] [cursor=pointer]
+ - tab [active] [selected] [ref=f2e343] [cursor=pointer]
+ - tabpanel "瀹氭爣" [ref=f2e350]:
+ - generic [ref=f2e351]:
+ - generic [ref=f2e353]:
+ - generic [ref=f2e354]: 宸插畾琛�
+ - generic [ref=f2e355]: "12"
+ - generic [ref=f2e357]: 10,000 鍏�
+ - text: 2026-07-29 14:52:43
+ - generic [ref=f2e358]: 宸插畾鏍�
+ - text: 寰呯敓鎴愮患鍚堣瘎瀹�
+ - generic [ref=f2e361]:
+ - generic "瀹氭爣瀹屾垚"
+ - button "鐢熸垚閲囪喘璁㈠崟" [ref=f2e362] [cursor=pointer]
+ - button "鍒犻櫎瀹氭爣" [ref=f2e366] [cursor=pointer]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T07-10-45-745Z.yml b/.playwright-mcp/page-2026-07-29T07-10-45-745Z.yml
new file mode 100644
index 0000000..987bb5e
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T07-10-45-745Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T07-12-31-548Z.yml b/.playwright-mcp/page-2026-07-29T07-12-31-548Z.yml
new file mode 100644
index 0000000..5dde7b1
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T07-12-31-548Z.yml
@@ -0,0 +1,121 @@
+- generic [ref=f3e7]:
+ - complementary [ref=f3e8]:
+ - generic [ref=f3e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f3e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f3e18]
+ - generic [ref=f3e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=f3e22]:
+ - listitem [ref=f3e23]:
+ - generic [ref=f3e24] [cursor=pointer]: 姒傝
+ - listitem [ref=f3e34]:
+ - generic [ref=f3e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=f3e43]:
+ - generic [ref=f3e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=f3e50]:
+ - generic [ref=f3e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=f3e57]:
+ - generic [ref=f3e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=f3e65]:
+ - generic [ref=f3e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=f3e74]:
+ - generic [ref=f3e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=f3e82]:
+ - generic [ref=f3e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=f3e89]:
+ - generic [ref=f3e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=f3e96]:
+ - generic [ref=f3e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=f3e103]:
+ - generic [ref=f3e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=f3e110]:
+ - generic [ref=f3e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=f3e117]:
+ - generic [ref=f3e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=f3e131]:
+ - generic [ref=f3e132]:
+ - banner [ref=f3e133]:
+ - button [ref=f3e134] [cursor=pointer]
+ - button [ref=f3e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=f3e142]:
+ - list [ref=f3e143]:
+ - listitem [ref=f3e144]:
+ - link [ref=f3e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=f3e152]
+ - listitem [ref=f3e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=f3e156]
+ - generic [ref=f3e159]:
+ - generic [ref=f3e161] [cursor=pointer]:
+ - generic [ref=f3e165]: 鎼滅储
+ - generic [ref=f3e166]: Ctrl K
+ - button [ref=f3e168] [cursor=pointer]
+ - button "dark" [ref=f3e173] [cursor=pointer]
+ - button [ref=f3e182] [cursor=pointer]
+ - button [ref=f3e188] [cursor=pointer]
+ - button [ref=f3e191] [cursor=pointer]
+ - button [ref=f3e197] [cursor=pointer]:
+ - button [ref=f3e199]
+ - button "宸ヤ綔鍙�" [ref=f3e203] [cursor=pointer]
+ - button "婧愮爜" [ref=f3e207] [cursor=pointer]
+ - generic [ref=f3e214]:
+ - generic [ref=f3e219]:
+ - generic [ref=f3e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=f3e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=f3e254] [cursor=pointer]
+ - main [ref=f3e272]:
+ - generic [ref=f3e275]:
+ - generic [ref=f3e276]:
+ - button [ref=f3e277] [cursor=pointer]
+ - generic [ref=f3e280]: 娴嬭瘯椤圭洰
+ - generic [ref=f3e281]: 娴嬭瘯缂栧彿
+ - generic [ref=f3e282]: 宸插畾鏍�
+ - generic [ref=f3e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=f3e284] [cursor=pointer]
+ - generic [ref=f3e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=f3e291]:
+ - generic [ref=f3e292]:
+ - generic [ref=f3e293]: 棰勭畻閲戦
+ - generic [ref=f3e294]: 15,000 鍏�
+ - generic [ref=f3e295]:
+ - generic [ref=f3e296]: 璐熻矗浜�
+ - generic [ref=f3e297]: "-"
+ - generic [ref=f3e298]:
+ - generic [ref=f3e299]: 閲囪喘缁勭粐
+ - generic [ref=f3e300]: "-"
+ - generic [ref=f3e301]:
+ - generic [ref=f3e302]: 寮�鏍囨椂闂�
+ - generic [ref=f3e303]: 2026-07-29 11:20:11
+ - generic [ref=f3e304]:
+ - generic [ref=f3e305]: 寮�濮嬫椂闂�
+ - generic [ref=f3e306]: 2026-07-29 11:13:02
+ - generic [ref=f3e307]:
+ - generic [ref=f3e308]: 鎴鏃堕棿
+ - generic [ref=f3e309]: 2026-07-29 19:13:06
+ - generic [ref=f3e313]:
+ - tablist [ref=f3e314]:
+ - generic [ref=f3e316]:
+ - tab [ref=f3e318] [cursor=pointer]
+ - tab [ref=f3e323] [cursor=pointer]
+ - tab [ref=f3e328] [cursor=pointer]
+ - tab [ref=f3e333] [cursor=pointer]
+ - tab [ref=f3e338] [cursor=pointer]
+ - tab [active] [selected] [ref=f3e343] [cursor=pointer]
+ - tabpanel "瀹氭爣" [ref=f3e404]:
+ - generic [ref=f3e405]:
+ - generic [ref=f3e407]:
+ - generic [ref=f3e408]: 宸插畾琛�
+ - generic [ref=f3e409]: "12"
+ - generic [ref=f3e411]: 10,000 鍏�
+ - text: 2026-07-29 14:52:43
+ - generic [ref=f3e412]: 宸茬敓鎴愯鍗�
+ - text: 18缁煎悎璇勫畾
+ - generic [ref=f3e415]:
+ - generic "璁㈠崟宸茬敓鎴�"
+ - button "鍒犻櫎瀹氭爣" [ref=f3e416] [cursor=pointer]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T07-14-24-445Z.yml b/.playwright-mcp/page-2026-07-29T07-14-24-445Z.yml
new file mode 100644
index 0000000..987bb5e
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T07-14-24-445Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T07-14-44-587Z.yml b/.playwright-mcp/page-2026-07-29T07-14-44-587Z.yml
new file mode 100644
index 0000000..cff9162
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T07-14-44-587Z.yml
@@ -0,0 +1,121 @@
+- generic [ref=f4e7]:
+ - complementary [ref=f4e8]:
+ - generic [ref=f4e9]:
+ - link "宸ュ巶鏁板瓧鍖朚OM绯荤粺 宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f4e15] [cursor=pointer]:
+ - /url: javascript:void 0
+ - img "宸ュ巶鏁板瓧鍖朚OM绯荤粺" [ref=f4e18]
+ - generic [ref=f4e19]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
+ - menu [ref=f4e22]:
+ - listitem [ref=f4e23]:
+ - generic [ref=f4e24] [cursor=pointer]: 姒傝
+ - listitem [ref=f4e34]:
+ - generic [ref=f4e35] [cursor=pointer]: 鍩虹鏁版嵁
+ - listitem [ref=f4e43]:
+ - generic [ref=f4e44] [cursor=pointer]: 閲囪喘绠$悊
+ - listitem [ref=f4e50]:
+ - generic [ref=f4e51] [cursor=pointer]: 钀ラ攢绠$悊
+ - listitem [ref=f4e57]:
+ - generic [ref=f4e58] [cursor=pointer]: 璁惧绠$悊
+ - listitem [ref=f4e65]:
+ - generic [ref=f4e66] [cursor=pointer]: 浠撳偍鐗╂祦
+ - listitem [ref=f4e74]:
+ - generic [ref=f4e75] [cursor=pointer]: 鐢熶骇绠℃帶
+ - listitem [ref=f4e82]:
+ - generic [ref=f4e83] [cursor=pointer]: 璐ㄩ噺绠$悊
+ - listitem [ref=f4e89]:
+ - generic [ref=f4e90] [cursor=pointer]: 鍗忓悓鍔炲叕
+ - listitem [ref=f4e96]:
+ - generic [ref=f4e97] [cursor=pointer]: 浜哄姏璧勬簮
+ - listitem [ref=f4e103]:
+ - generic [ref=f4e104] [cursor=pointer]: 鍩虹璁炬柦
+ - listitem [ref=f4e110]:
+ - generic [ref=f4e111] [cursor=pointer]: 绯荤粺绠$悊
+ - listitem [ref=f4e117]:
+ - generic [ref=f4e118] [cursor=pointer]: IM 鍗虫椂閫氳
+ - generic [ref=f4e131]:
+ - generic [ref=f4e132]:
+ - banner [ref=f4e133]:
+ - button [ref=f4e134] [cursor=pointer]
+ - button [ref=f4e137] [cursor=pointer]
+ - navigation "breadcrumb" [ref=f4e142]:
+ - list [ref=f4e143]:
+ - listitem [ref=f4e144]:
+ - link [ref=f4e145] [cursor=pointer]:
+ - /url: javascript:void 0
+ - listitem [ref=f4e152]
+ - listitem [ref=f4e155]:
+ - link "鎷涙爣璇︽儏" [disabled] [ref=f4e156]
+ - generic [ref=f4e159]:
+ - generic [ref=f4e161] [cursor=pointer]:
+ - generic [ref=f4e165]: 鎼滅储
+ - generic [ref=f4e166]: Ctrl K
+ - button [ref=f4e168] [cursor=pointer]
+ - button "dark" [ref=f4e173] [cursor=pointer]
+ - button [ref=f4e182] [cursor=pointer]
+ - button [ref=f4e188] [cursor=pointer]
+ - button [ref=f4e191] [cursor=pointer]
+ - button [ref=f4e197] [cursor=pointer]:
+ - button [ref=f4e199]
+ - button "宸ヤ綔鍙�" [ref=f4e203] [cursor=pointer]
+ - button "婧愮爜" [ref=f4e207] [cursor=pointer]
+ - generic [ref=f4e214]:
+ - generic [ref=f4e219]:
+ - generic [ref=f4e220] [cursor=pointer]: 鍒嗘瀽椤�
+ - generic [ref=f4e238] [cursor=pointer]: 鎷涙爣璇︽儏
+ - button [ref=f4e254] [cursor=pointer]
+ - main [ref=f4e272]:
+ - generic [ref=f4e275]:
+ - generic [ref=f4e276]:
+ - button [ref=f4e277] [cursor=pointer]
+ - generic [ref=f4e280]: 娴嬭瘯椤圭洰
+ - generic [ref=f4e281]: 娴嬭瘯缂栧彿
+ - generic [ref=f4e282]: 宸插畾鏍�
+ - generic [ref=f4e283]: 鍏紑鎷涙爣
+ - button "鍒锋柊" [ref=f4e284] [cursor=pointer]
+ - generic [ref=f4e290]:
+ - generic:
+ - generic "鑽夌"
+ - generic "鍙戝竷"
+ - generic "鎶曟爣涓�"
+ - generic "璇勬爣涓�"
+ - generic "宸插畾鏍�"
+ - generic [ref=f4e291]:
+ - generic [ref=f4e292]:
+ - generic [ref=f4e293]: 棰勭畻閲戦
+ - generic [ref=f4e294]: 15,000 鍏�
+ - generic [ref=f4e295]:
+ - generic [ref=f4e296]: 璐熻矗浜�
+ - generic [ref=f4e297]: "-"
+ - generic [ref=f4e298]:
+ - generic [ref=f4e299]: 閲囪喘缁勭粐
+ - generic [ref=f4e300]: "-"
+ - generic [ref=f4e301]:
+ - generic [ref=f4e302]: 寮�鏍囨椂闂�
+ - generic [ref=f4e303]: 2026-07-29 11:20:11
+ - generic [ref=f4e304]:
+ - generic [ref=f4e305]: 寮�濮嬫椂闂�
+ - generic [ref=f4e306]: 2026-07-29 11:13:02
+ - generic [ref=f4e307]:
+ - generic [ref=f4e308]: 鎴鏃堕棿
+ - generic [ref=f4e309]: 2026-07-29 19:13:06
+ - generic [ref=f4e313]:
+ - tablist [ref=f4e314]:
+ - generic [ref=f4e316]:
+ - tab [ref=f4e318] [cursor=pointer]
+ - tab [ref=f4e323] [cursor=pointer]
+ - tab [ref=f4e328] [cursor=pointer]
+ - tab [ref=f4e333] [cursor=pointer]
+ - tab [ref=f4e338] [cursor=pointer]
+ - tab [selected] [ref=f4e343] [cursor=pointer]
+ - tabpanel "瀹氭爣" [active] [ref=f4e404]:
+ - generic [ref=f4e405]:
+ - generic [ref=f4e407]:
+ - generic [ref=f4e408]: AWARD2024070001
+ - generic [ref=f4e409]: "12"
+ - generic [ref=f4e411]: 10,000 鍏�
+ - text: 2026-07-29 14:52:43
+ - generic [ref=f4e412]: 宸茬敓鎴愯鍗�
+ - text: 18缁煎悎璇勫畾
+ - generic [ref=f4e415]:
+ - generic "璁㈠崟宸茬敓鎴�"
+ - button "鍒犻櫎瀹氭爣" [ref=f4e416] [cursor=pointer]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-07-29T07-14-58-476Z.yml b/.playwright-mcp/page-2026-07-29T07-14-58-476Z.yml
new file mode 100644
index 0000000..987bb5e
--- /dev/null
+++ b/.playwright-mcp/page-2026-07-29T07-14-58-476Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ュ巶鏁板瓧鍖朚OM绯荤粺
\ No newline at end of file
diff --git a/docs/aftersales_frontend_integration.md b/docs/aftersales_frontend_integration.md
new file mode 100644
index 0000000..7144ac3
--- /dev/null
+++ b/docs/aftersales_frontend_integration.md
@@ -0,0 +1,636 @@
+# 鍞悗宸ュ崟妯″潡 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰锛堝叏閮ㄦ柊寤猴級
+
+| 椤甸潰 | 璺敱寤鸿 | 璇存槑 |
+|------|---------|------|
+| 鍞悗宸ュ崟鍒楄〃 | `/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 閿�鍞鍗曟帴鍙f煡璇�
+
+---
+
+## 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 | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+
+**鍝嶅簲锛�** `{ "code": 0, "data": true }`
+
+#### 1.4 鑾峰彇宸ュ崟璇︽儏 `GET /aftersales/ticket/get`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| 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 | 瑙e喅鏃堕棿 |
+| 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-鎴戣礋璐g殑 2-鎴戝弬涓庣殑 3-涓嬪睘璐熻矗鐨� 4-鍏ㄩ儴 |
+
+**鍝嶅簲锛�**
+```json
+{
+ "code": 0,
+ "data": {
+ "list": [ /* AfterSaleTicketRespVO 鏁扮粍 */ ],
+ "total": 100
+ }
+}
+```
+
+**娉ㄦ剰锛�** 鍒嗛〉鍒楄〃鍝嶅簲涓殑姣忔潯璁板綍缁撴瀯涓庤鎯呮帴鍙d竴鑷达紙鍚� `items` 鍜屽叧鑱斿悕绉板瓧娈碉級锛屼絾鍒楄〃鍦烘櫙涓� `items` 鍙负绌恒��
+
+#### 1.6 鍒嗛厤澶勭悊浜� `POST /aftersales/ticket/assign`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+| handlerUserId | Long | 鏄� | 澶勭悊浜虹敤鎴风紪鍙凤紙Query 鍙傛暟锛� |
+
+**涓氬姟瑙勫垯锛�** 浠呰崏绋裤�佸鐞嗕腑鐘舵�佸彲鍒嗛厤銆傚垎閰嶅悗鐘舵�佽嚜鍔ㄥ彉涓�"澶勭悊涓�"銆�
+
+#### 1.7 闂鍒ゅ畾 `POST /aftersales/ticket/classify`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+| issueType | Integer | 鏄� | 闂绫诲瀷锛�1-涓�鑸棶棰� 2-缁翠慨闂 3-閫�璐ч棶棰� |
+| issueCategory | String | 鍚� | 闂鍒嗙被锛堝"澶栬缂洪櫡"銆�"鍔熻兘鏁呴殰"绛夛級 |
+| severityLevel | Integer | 鍚� | 涓ラ噸绋嬪害锛�0-杞诲井 1-涓�鑸� 2-涓ラ噸 3-鑷村懡 |
+
+**涓氬姟瑙勫垯锛�** 浠呭鐞嗕腑鐘舵�佸彲鍒ゅ畾銆傚垽瀹氫负閫�璐ч棶棰樺悗锛屽簲寮曞鐢ㄦ埛鍒涘缓閫�璐х敵璇枫��
+
+#### 1.8 瀹岀粨宸ュ崟 `PUT /aftersales/ticket/resolve`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+
+**涓氬姟瑙勫垯锛�** 浠呭鐞嗕腑/寰呴��璐�/寰呴��娆剧姸鎬佸彲瀹岀粨銆�
+
+#### 1.9 鍏抽棴宸ュ崟 `PUT /aftersales/ticket/close`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+
+**涓氬姟瑙勫垯锛�** 浠呭凡瀹岀粨鐘舵�佸彲鍏抽棴銆�
+
+#### 1.10 鍙栨秷宸ュ崟 `PUT /aftersales/ticket/cancel`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 宸ュ崟缂栧彿锛圦uery 鍙傛暟锛� |
+
+**涓氬姟瑙勫垯锛�** 浠呰崏绋裤�佸鐞嗕腑鐘舵�佸彲鍙栨秷銆�
+
+---
+
+### 浜屻�佸敭鍚庨��璐х敵璇�
+
+| 鏂规硶 | 璺緞 | 鏉冮檺 | 璇存槑 |
+|------|------|------|------|
+| 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 鍒涘缓閿�鍞��璐у崟锛岀姸鎬佸彉涓�"瀹℃牳閫氳繃-宸插悓姝ES(20)"
+- 鍚庣画 MES 閫�璐ф祦绋嬶紙璐ㄦ鈫掑叆搴撯啋閫�娆撅級鐢卞悗绔洖璋冭嚜鍔ㄥ畬鎴愶紝鍓嶇鏃犻渶鎿嶄綔
+
+#### 2.3 鑾峰彇閫�璐ц鎯� `GET /aftersales/return/get`
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 閫�璐х敵璇风紪鍙凤紙Query 鍙傛暟锛� |
+
+**鍝嶅簲瀛楁锛堟柊澧�/閲嶈瀛楁锛夛細**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 閫�璐х敵璇风紪鍙� |
+| no | String | 閫�璐у崟鍙凤紙鏍煎紡锛歊T + 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 | 閫�璐ч棶棰� | 鍒涘缓閫�璐х敵璇封啋瀹℃牳鈫扢ES閫�璐р啋瀹岀粨 |
+
+### 閫�璐х姸鎬� (AfterSaleReturnStatusEnum)
+
+| 鍊� | 鍚嶇О | 璇存槑 |
+|----|------|------|
+| 0 | 鑽夌 | 鍙鏍� |
+| 10 | 閿�鍞鏍镐腑 | 鈥� |
+| 20 | 瀹℃牳閫氳繃-宸插悓姝ES | 绛夊緟MES澶勭悊 |
+| 30 | 妫�楠屼腑 | MES璐ㄦ杩涜涓� |
+| 40 | 妫�楠屽畬鎴� | 绛夊緟鍏ュ簱 |
+| 50 | 宸插叆搴� | 绛夊緟閫�娆� |
+| 60 | 閫�娆�/鎹㈣揣瀹屾垚 | 鈥� |
+| 70 | 宸插叧闂� | 缁堟�� |
+| 99 | 宸插彇娑� | 缁堟�� |
+
+### 閫�璐х被鍨�
+
+| 鍊� | 鍚嶇О |
+|----|------|
+| 1 | 閫�娆鹃��璐� |
+| 2 | 鎹㈣揣 |
+| 3 | 浠呴��娆� |
+
+### 涓ラ噸绋嬪害
+
+| 鍊� | 鍚嶇О | 寤鸿棰滆壊 |
+|----|------|----------|
+| 0 | 杞诲井 | 缁胯壊 |
+| 1 | 涓�鑸� | 钃濊壊 |
+| 2 | 涓ラ噸 | 姗欒壊 |
+| 3 | 鑷村懡 | 绾㈣壊 |
+
+### 鍦烘櫙绫诲瀷 (sceneType)
+
+| 鍊� | 鍚嶇О | 璇存槑 |
+|----|------|------|
+| 1 | 鎴戣礋璐g殑 | 褰撳墠鐢ㄦ埛涓鸿礋璐d汉 |
+| 2 | 鎴戝弬涓庣殑 | 褰撳墠鐢ㄦ埛涓哄鐞嗕汉鎴栬鎺堟潈 |
+| 3 | 涓嬪睘璐熻矗鐨� | 涓嬪睘涓鸿礋璐d汉 |
+| 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 | 鍩烘湰淇℃伅 | 瑙e喅鏃堕棿锛堝畬缁撳悗鏄剧ず锛� |
+| 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) | 宸插悓姝ES(20) | 妫�楠屼腑(30) | 妫�楠屽畬鎴�(40) | 宸插叆搴�(50) | 閫�娆惧畬鎴�(60) |
+|------|---------|------------|---------------|------------|--------------|------------|--------------|
+| 瀹℃牳閫氳繃 | 鉁� | 鉂� | 鉂� | 鉂� | 鉂� | 鉂� | 鉂� |
+| 鏌ョ湅璇︽儏 | 鉁� | 鉁� | 鉁� | 鉁� | 鉁� | 鉁� | 鉁� |
+
+### 鍒嗛〉鏌ヨ鏁版嵁鏉冮檺璇存槑
+
+- **sceneType=1锛堟垜璐熻矗鐨勶級**锛氭樉绀哄綋鍓嶇敤鎴蜂负 `ownerUserId` 鐨勫伐鍗� + 鏁版嵁浜ゆ帴鐨勫伐鍗�
+- **sceneType=2锛堟垜鍙備笌鐨勶級**锛氭樉绀哄綋鍓嶇敤鎴锋湁璇�/鍐欐潈闄愮殑宸ュ崟
+- **sceneType=3锛堜笅灞炶礋璐g殑锛�**锛氭樉绀哄綋鍓嶇敤鎴风殑涓嬪睘璐熻矗鐨勫伐鍗�
+- **sceneType=4锛堝叏閮級**锛氶渶瑕佺鐞嗗憳鏉冮檺锛坄aftersales:admin`锛�
+
+---
+
+## 鏂囦欢涓婁紶
+
+鍞悗妯″潡浣跨敤缁熶竴鐨勬枃浠朵笂浼犳満鍒讹紝涓嶈嚜琛屽疄鐜颁笂浼犳帴鍙c��
+
+**涓婁紶娴佺▼锛�**
+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`
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+1. **鏄庣粏琛屾洿鏂扮瓥鐣�**锛氭洿鏂板伐鍗曟椂锛屾槑缁嗚閲囩敤"鍏堝垹鍚庡"绛栫暐锛屽墠绔繀椤讳紶鍏ュ畬鏁寸殑鏄庣粏琛屽垪琛紙鍖呮嫭鏈慨鏀圭殑琛岋級锛屽惁鍒欎細瀵艰嚧鏄庣粏琛屼涪澶便��
+2. **鍗曞彿鐢熸垚**锛氬伐鍗曞崟鍙凤紙AS鍓嶇紑锛夊拰閫�璐у崟鍙凤紙RT鍓嶇紑锛夌敱鍚庣鑷姩鐢熸垚锛屽墠绔笉闇�瑕佷紶鍏ャ��
+3. **鐘舵�佹祦杞�**锛氭墍鏈夌姸鎬佸彉鏇存搷浣滐紙鍒嗛厤銆佸垽瀹氥�佸畬缁撱�佸叧闂�佸彇娑堛�佸鏍革級鍚庣閮戒細鏍¢獙褰撳墠鐘舵�佹槸鍚﹀厑璁革紝鍓嶇搴旀牴鎹寜閽樉闅愯鍒欐帶鍒舵搷浣滃叆鍙c��
+4. **閫�璐у鏍稿悗鑷姩鍚屾 MES**锛氬鏍搁�氳繃閫�璐х敵璇峰悗锛屽悗绔嚜鍔ㄨ皟鐢� MES 鍒涘缓閿�鍞��璐у崟锛屽悗缁� MES 娴佺▼锛堣川妫�鈫掑叆搴撯啋閫�娆撅級鐢卞洖璋冭嚜鍔ㄦ帹杩涚姸鎬侊紝鍓嶇鍙渶杞鍒锋柊鏌ョ湅鏈�鏂扮姸鎬併��
+5. **鏁版嵁甯﹀叆**锛氬垱寤洪��璐х敵璇锋椂锛屽繀椤讳粠宸ュ崟璇︽儏椤佃烦杞苟甯﹀叆 `ticketId`銆乣customerId`銆乣saleOrderId` 浠ュ強宸ュ崟鏄庣粏琛屼腑鏍囪涓�"闇�瑕侀��璐�"鐨勮銆�
+6. **鏉冮檺鎺у埗**锛氭墍鏈夋帴鍙i兘鏈� `@PreAuthorize` 鏉冮檺鏍¢獙锛屽墠绔彍鍗曞拰鎸夐挳闇�瑕佸搴旈厤缃潈闄愭爣璇嗐��
+7. **璺敱鏉冮檺**锛氶渶鍦� `src/router/routes/modules/` 涓嬫柊寤� `aftersales.ts` 璺敱妯″潡鏂囦欢锛屽苟鍦ㄨ彍鍗曚腑娉ㄥ唽"鍞悗鏈嶅姟"涓�绾ц彍鍗曘��
+8. **API 妯″潡**锛氶渶鍦� `src/api/` 涓嬫柊寤� `aftersales/` 鐩綍锛屽垱寤� `ticket.ts` 鍜� `return.ts` 涓や釜 API 鏂囦欢銆�
diff --git a/docs/crm_customer_view_all.md b/docs/crm_customer_view_all.md
new file mode 100644
index 0000000..adb890c
--- /dev/null
+++ b/docs/crm_customer_view_all.md
@@ -0,0 +1,88 @@
+# 鏌ョ湅鍏ㄩ儴瀹㈡埛鏉冮檺 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 璺敱璺緞 | 璇存槑 |
+|------|----------|------|
+| 瀹㈡埛绠$悊鍒楄〃 | `/crm/customer` | 瀹㈡埛鍒楄〃椤碉紝鏂板"鍏ㄩ儴"Tab |
+
+---
+
+## API
+
+### 宸叉湁鎺ュ彛锛氬鎴峰垎椤垫煡璇紙鏃犻渶鏂板鎺ュ彛锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/crm/customer/page` | 瀹㈡埛鍒嗛〉鏌ヨ |
+
+**宸叉湁璇锋眰鍙傛暟锛坄CrmCustomerPageReqVO`锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| sceneType | Integer | 鍚� | 鍦烘櫙绫诲瀷锛�1-鎴戣礋璐g殑 / 2-鎴戝弬涓庣殑 / 3-涓嬪睘璐熻矗鐨� / **4-鍏ㄩ儴锛堟柊澧烇級** |
+| name | String | 鍚� | 瀹㈡埛鍚嶇О锛堟ā绯婏級 |
+| mobile | String | 鍚� | 鎵嬫満 |
+| industryId | Integer | 鍚� | 鎵�灞炶涓� |
+| level | Integer | 鍚� | 瀹㈡埛绛夌骇 |
+| source | Integer | 鍚� | 瀹㈡埛鏉ユ簮 |
+| pool | Boolean | 鍚� | 鏄惁涓哄叕娴锋暟鎹� |
+| followUpStatus | Boolean | 鍚� | 璺熻繘鐘舵�� |
+
+**鍝嶅簲鏃犲彉鍖�**锛宍CrmCustomerRespVO` 宸插寘鍚細
+- `ownerUserName` 鈥� 閿�鍞汉鍛樺悕绉�
+- `ownerUserDeptName` 鈥� 閿�鍞汉鍛橀儴闂ㄥ悕绉�
+
+---
+
+## 涓氬姟瑙勫垯璇存槑
+
+### 鍦烘櫙绫诲瀷鎵╁睍
+
+| sceneType 鍊� | 鍦烘櫙鍚嶇О | 鏁版嵁鑼冨洿 | 鏉冮檺瑕佹眰 |
+|--------------|----------|----------|----------|
+| 1 | 鎴戣礋璐g殑 | `owner_user_id = 褰撳墠鐢ㄦ埛` | `crm:customer:query` |
+| 2 | 鎴戝弬涓庣殑 | 鍥㈤槦鎴愬憳锛堥潪璐熻矗浜猴級 | `crm:customer:query` |
+| 3 | 涓嬪睘璐熻矗鐨� | 涓嬪睘涓鸿礋璐d汉 | `crm:customer:query` |
+| **4锛堟柊澧烇級** | **鍏ㄩ儴** | **鎵�鏈夊鎴凤紙鏃犺繃婊わ級** | **`crm:customer:view-all` 鎴� `crm_admin` 瑙掕壊** |
+
+### 鏉冮檺闄嶇骇瑙勫垯
+
+褰撶敤鎴蜂互 `sceneType=4` 璇锋眰鏁版嵁鏃讹細
+- 鐢ㄦ埛鎷ユ湁 `crm:customer:view-all` 鏉冮檺 **鎴�** `crm_admin` 瑙掕壊 鈫� 杩斿洖鍏ㄩ儴瀹㈡埛
+- 鐢ㄦ埛**娌℃湁**璇ユ潈闄� 鈫� 鑷姩闄嶇骇涓�"鎴戣礋璐g殑"锛坄sceneType=1`锛夛紝浠呰繑鍥炶嚜宸辩殑瀹㈡埛
+
+### 鏉冮檺鏍囪瘑
+
+| 鏉冮檺鏍囪瘑 | 璇存槑 | 鎵�灞炶彍鍗� |
+|----------|------|----------|
+| `crm:customer:view-all` | 鏌ョ湅鍏ㄩ儴瀹㈡埛 | CRM 瀹㈡埛鍏捣 |
+
+璇ユ潈闄愬凡娣诲姞鍒版暟鎹簱 `system_menu` 琛紝浣嶄簬銆屽鎴峰叕娴枫�嶈彍鍗曚笅锛坕d=602583锛夛紝瑙掕壊鍒嗛厤鍚庡嵆鍙敓鏁堛��
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 瀹㈡埛鍒楄〃椤� Tab 鍒囨崲
+
+鍦ㄧ幇鏈夌殑鍦烘櫙绫诲瀷 Tab锛堟垜璐熻矗鐨�/鎴戝弬涓庣殑/涓嬪睘璐熻矗鐨勶級鍚庨潰锛屾柊澧�"鍏ㄩ儴"Tab锛坄sceneType=4`锛夈��
+
+**灞曠ず瑙勫垯**锛�
+- 浠呭綋鐢ㄦ埛鎷ユ湁 `crm:customer:view-all` 鏉冮檺鎴� `crm_admin` 瑙掕壊鏃讹紝鎵嶆樉绀�"鍏ㄩ儴"Tab
+- 鍒ゆ柇鏂瑰紡锛氳皟鐢ㄦ潈闄愬垽鏂帴鍙f垨浠庣敤鎴锋潈闄愬垪琛ㄤ腑妫�鏌�
+
+### 2. 鏉冮檺鍒ゆ柇
+
+鍓嶇闇�瑕佸垽鏂綋鍓嶇敤鎴锋槸鍚︽湁 `crm:customer:view-all` 鏉冮檺锛�
+- 鏂瑰紡涓�锛氶�氳繃 Vue 鎸囦护 `v-hasPermi="['crm:customer:view-all']"` 鎺у埗 Tab 鏄鹃殣
+- 鏂瑰紡浜岋細閫氳繃鑾峰彇鐢ㄦ埛鏉冮檺鍒楄〃鍒ゆ柇
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+- "鍏ㄩ儴"Tab 浠呮帶鍒舵暟鎹彲瑙佽寖鍥达紝涓嶆敼鍙樺叾浠栧姛鑳芥潈闄愶紙濡傜紪杈戙�佸垹闄や粛闇�瀵瑰簲鏉冮檺锛�
+- 瀵煎嚭 Excel 鏃跺悓鏍锋敮鎸� `sceneType=4`锛屾嫢鏈夋潈闄愮殑鐢ㄦ埛鍙鍑哄叏閮ㄥ鎴锋暟鎹�
+- 鏉冮檺鏁版嵁宸叉彃鍏ユ暟鎹簱锛坄system_menu` id=602583锛夛紝鏃犻渶鍓嶇棰濆澶勭悊鏉冮檺娉ㄥ唽
+- 璇ユ潈闄愪笌 `crm_admin` 瑙掕壊鐨勫尯鍒細`crm_admin` 鎷ユ湁鎵�鏈� CRM 绠$悊鏉冮檺锛岃�� `view-all` 浠呮帶鍒跺鎴峰垪琛ㄧ殑鏌ョ湅鑼冨洿
diff --git a/docs/document_audit_project_filter_fix.md b/docs/document_audit_project_filter_fix.md
new file mode 100644
index 0000000..c3a69ca
--- /dev/null
+++ b/docs/document_audit_project_filter_fix.md
@@ -0,0 +1,59 @@
+# 椤圭洰璇︽儏 - 璧勬枡瀹℃牳鏌ヨ淇锛堝墠绔仈璋冩柟妗堬級
+
+## 闂
+
+椤圭洰璇︽儏椤�"璧勬枡瀹℃牳" Tab 鏌ヨ `/mes/pd/document-audit/page` 鏃舵湭浼犲叆 `projectId`锛屽鑷磋繑鍥炰簡鎵�鏈夐」鐩殑瀹℃牳璁板綍锛岃�岄潪褰撳墠椤圭洰鐨勩��
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 璺敱 | 璇存槑 |
+|------|------|------|
+| 椤圭洰璇︽儏 - 璧勬枡瀹℃牳 Tab | `/mes/pd/project/detail/{id}` | 寰呬慨澶� |
+
+---
+
+## API 鍙樻洿
+
+`GET /mes/pd/document-audit/page` 鈥� 鏂板璇锋眰鍙傛暟锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| projectId | Long | 鍚� | 椤圭洰 ID锛屼紶鍏ュ悗浠呰繑鍥炶椤圭洰涓嬭祫鏂欑殑瀹℃牳璁板綍 |
+
+**鍝嶅簲缁撴瀯涓嶅彉銆�**
+
+---
+
+## 鍓嶇淇敼鐐�
+
+**鏂囦欢**: `src/views/mes/pd/project/modules/document-audit-list.vue`
+
+璇ョ粍浠跺凡閫氳繃 `props.projectId` 鎺ユ敹浜嗛」鐩� ID锛屼絾鏈紶缁欐帴鍙c�傚湪 `proxyConfig.ajax.query` 涓姞涓婂嵆鍙細
+
+```typescript
+// 绗� 62-67 琛岋紝鍘熸潵鐨勶細
+query: async ({ page }, formValues) => {
+ return await getPdDocumentAuditPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+},
+
+// 鏀逛负锛�
+query: async ({ page }, formValues) => {
+ return await getPdDocumentAuditPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ projectId: props.projectId,
+ ...formValues,
+ });
+},
+```
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+- 璇ョ粍浠跺湪椤圭洰鐘舵�佷负 `PENDING_AUDIT`锛堝緟瀹℃牳锛夋椂灞曠ず锛岄�氳繃 props 鎺ユ敹 `projectId`
+- 鏀瑰姩浠呴渶涓�琛岋紝鏃犻渶淇敼 API 灏佽鍑芥暟鎴栫被鍨嬪畾涔�
diff --git a/docs/machinery_new_fields_frontend.md b/docs/machinery_new_fields_frontend.md
new file mode 100644
index 0000000..32a1b30
--- /dev/null
+++ b/docs/machinery_new_fields_frontend.md
@@ -0,0 +1,140 @@
+# 璁惧鍙拌处鏂板瀛楁 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 璺敱璺緞 | 璇存槑 |
+|------|----------|------|
+| 璁惧鍙拌处鍒楄〃 | `/mes/dv-machinery` | 鍒楄〃鏂板绛涢�夋潯浠躲�佽〃鏍煎垪 |
+| 璁惧鍙拌处琛ㄥ崟 | `/mes/dv-machinery` (寮圭獥) | 鏂板/缂栬緫琛ㄥ崟涓鍔� 3 涓瓧娈� |
+| 璁惧鍙拌处瀵煎叆 | 鍚屼笂锛堝鍏ュ脊绐楋級 | Excel 妯℃澘鏂板 3 鍒� |
+
+---
+
+## 鏂板瀛楁璇存槑
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| `productionEfficiency` | BigDecimal | 鍚� | 鐢熶骇鏁堢巼锛屽崟浣嶏細姣忓皬鏃剁敓浜ф暟閲忥紝鐢ㄤ簬涓嬫帓浜ц嚜鍔ㄨ绠楃敓浜ф椂闂� |
+| `conveyorBelt` | Integer | 鍚� | 浼犻�佸饱甯︽暟閲忥紝渚嬶細2 浠h〃鍙屽�嶆晥鐜� |
+| `workstationId` | Long | 鍚� | 宸ヤ綔绔欑紪鍙凤紝鐢ㄤ簬灏嗚澶囩粦瀹氬埌鎸囧畾宸ヤ綔绔� |
+
+---
+
+## API
+
+### 宸叉湁鎺ュ彛锛堟棤闇�鏂板锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/mes/dv/machinery/create` | 鍒涘缓璁惧 |
+| PUT | `/mes/dv/machinery/update` | 鏇存柊璁惧 |
+| GET | `/mes/dv/machinery/get?id={id}` | 鑾峰彇璇︽儏 |
+| GET | `/mes/dv/machinery/page` | 鍒嗛〉鏌ヨ |
+| GET | `/mes/dv/machinery/simple-list` | 绮剧畝鍒楄〃锛堜笅鎷夐�夐」锛� |
+| POST | `/mes/dv/machinery/import` | 瀵煎叆璁惧 |
+| GET | `/mes/dv/machinery/get-import-template` | 涓嬭浇瀵煎叆妯℃澘 |
+
+### 璇锋眰鍙傛暟 - 鍒涘缓/鏇存柊锛坄MesDvMachinerySaveReqVO`锛�
+
+**鏂板瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| productionEfficiency | BigDecimal | 鍚� | 鐢熶骇鏁堢巼锛堟瘡鍒嗛挓锛� |
+| conveyorBelt | Integer | 鍚� | 浼犻�佸饱甯︽暟閲� |
+| workstationId | Long | 鍚� | 宸ヤ綔绔欑紪鍙� |
+
+### 鍒嗛〉鏌ヨ鍙傛暟锛坄MesDvMachineryPageReqVO`锛�
+
+**鏂板绛涢�夊瓧娈碉細**
+
+| 瀛楁 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| workstationId | Long | 鍚� | 鎸夊伐浣滅珯绛涢�� |
+
+### 鍝嶅簲锛坄MesDvMachineryRespVO`锛�
+
+**鏂板瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| productionEfficiency | BigDecimal | 鐢熶骇鏁堢巼 |
+| conveyorBelt | Integer | 浼犻�佸饱甯� |
+| workstationId | Long | 宸ヤ綔绔欑紪鍙� |
+| workstationName | String | 宸ヤ綔绔欏悕绉� |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "id": 1,
+ "name": "CNC 鍔犲伐涓績",
+ "code": "EQ-001",
+ "productionEfficiency": 10.5,
+ "conveyorBelt": 2,
+ "workstationId": 300,
+ "workstationName": "涓�鍙峰伐浣滅珯",
+ ...
+ }
+}
+```
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鍒楄〃椤� - 绛涢�夐」
+
+鍦ㄥ垪琛ㄦ悳绱㈡爮鏂板"宸ヤ綔绔�"涓嬫媺绛涢�夋锛岃皟鐢� `/mes/md-workstation/simple-list` 鑾峰彇閫夐」銆�
+
+### 2. 鍒楄〃椤� - 琛ㄦ牸鍒�
+
+| 鏂板鍒� | 缁戝畾瀛楁 | 璇存槑 |
+|--------|----------|------|
+| 鐢熶骇鏁堢巼 | `productionEfficiency` | 鏄剧ず鏁板�� |
+| 浼犻�佸饱甯� | `conveyorBelt` | 鏄剧ず鏁板�� |
+| 宸ヤ綔绔� | `workstationName` | 鏄剧ず宸ヤ綔绔欏悕绉� |
+
+### 3. 琛ㄥ崟椤� - 鏂板/缂栬緫
+
+鍦ㄨ〃鍗曚腑鏂板 3 涓瓧娈碉細
+
+| 瀛楁 | 缁勪欢 | 璇存槑 |
+|------|------|------|
+| 鐢熶骇鏁堢巼 | `<el-input-number>` | 鏀寔灏忔暟锛屽崟浣嶆彁绀�"姣忓皬鏃�" |
+| 浼犻�佸饱甯� | `<el-input-number>` | 姝f暣鏁帮紝鎻愮ず"2=鍙屽�嶆晥鐜�" |
+| 宸ヤ綔绔� | `<el-select>` | 涓嬫媺閫夋嫨锛岃皟鐢ㄥ伐浣滅珯绮剧畝鍒楄〃鎺ュ彛 |
+
+### 4. 瀵煎叆妯℃澘
+
+瀵煎叆 Excel 妯℃澘鏂板 3 鍒楋紙鍙�夊垪锛夛細
+
+| 鍒楀悕 | 璇存槑 |
+|------|------|
+| 鐢熶骇鏁堢巼(姣忓皬鏃�) | 鏁板瓧锛屽彲閫� |
+| 浼犻�佸饱甯� | 鏁存暟锛屽彲閫� |
+| 宸ヤ綔绔欑紪鐮� | 宸ヤ綔绔欑紪鐮侊紝鍙�夛紝濉簡鍒欐牎楠屾槸鍚﹀瓨鍦� |
+
+---
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 瑙勫垯 | 璇存槑 |
+|------|------|
+| 鐢熶骇鏁堢巼鍗曚綅 | 鍥哄畾涓�"姣忓皬鏃剁敓浜ф暟閲�"锛屽墠绔洿鎺ュ睍绀�"姣忓皬鏃�" |
+| 浼犻�佸饱甯﹀惈涔� | 鏁板�� 2 浠h〃鍙屽�嶆晥鐜囷紝鎺掍骇鏃� `conveyorBelt 脳 productionEfficiency` 寰楀埌鏈�缁堜骇鑳� |
+| 宸ヤ綔绔欑粦瀹� | 涓�鍙拌澶囧彲缁戝畾涓�涓伐浣滅珯锛堥潪寮哄埗锛夛紝涓嬫媺鏁版嵁婧愪负 `/mes/md-workstation/simple-list` |
+| 瀵煎叆鏃跺伐浣滅珯 | 濉叆宸ヤ綔绔欑紪鐮侊紙闈� ID锛夛紝绯荤粺鑷姩杞崲锛岀紪鐮佷笉瀛樺湪鍒欏鍏ュけ璐� |
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+- 3 涓柊瀛楁鍧囦负鍙�夛紝涓嶅奖鍝嶇幇鏈夋暟鎹�
+- 鐢熶骇鏁堢巼杈撳叆寤鸿闄愬埗鏈�灏忓�� 0锛屼繚鐣� 2 浣嶅皬鏁�
+- 浼犻�佸饱甯︿粎鍏佽姝f暣鏁�
+- 宸ヤ綔绔欎笅鎷変娇鐢ㄥ凡鏈夋帴鍙� `/mes/md-workstation/simple-list`锛屾棤闇�鏂板
+- Excel 瀵煎叆鏃跺伐浣滅珯鍒楀~缂栫爜鑰岄潪 ID
+- 瀵煎叆妯℃澘鍚庣鎺ュ彛 `/mes/dv/machinery/get-import-template` 涔熶細杩斿洖鏂板垪
diff --git a/docs/pro_archive_integration.md b/docs/pro_archive_integration.md
new file mode 100644
index 0000000..4667d21
--- /dev/null
+++ b/docs/pro_archive_integration.md
@@ -0,0 +1,154 @@
+# 鐢熶骇娴佺▼ - 鏌ョ湅浜у搧璁捐褰掓。鍙婇檮浠讹紙鍓嶇鑱旇皟鏂规锛�
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 璺敱璺緞 | 璇存槑 |
+|------|----------|------|
+| 鐢熶骇宸ュ崟鍒楄〃/璇︽儏 | `/mes/pro-workorder` | 鏂板"鏌ョ湅褰掓。"鎸夐挳 |
+| 鐢熶骇宸ュ崟涓績 | `/mes/workorder-center` | 鏂板"鏌ョ湅褰掓。"鎸夐挳 |
+| 鐢熶骇浠诲姟鍒楄〃/璇︽儏 | `/mes/pro-task` | 鏂板"鏌ョ湅褰掓。"鎸夐挳 |
+| 寰呮姤宸ュ彴璐� | `/mes/pro-task` (pending-feedback) | 鏂板"鏌ョ湅褰掓。"鎸夐挳 |
+| 鐢熶骇鎶ュ伐鍒楄〃/璇︽儏 | `/mes/pro-feedback` | 鏂板"鏌ョ湅褰掓。"鎸夐挳 |
+
+---
+
+## API锛堝凡鏈夋帴鍙o紝鏃犻渶鏂板锛�
+
+### 褰掓。鍒嗛〉鍒楄〃
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/mes/pd/archive/page` | 鎸変骇鍝佺紪鐮佺瓫閫夊綊妗e垪琛� |
+
+**璇锋眰鍙傛暟锛堝叧閿級锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| productCode | String | 鏄� | 浜у搧缂栫爜锛屼粠褰撳墠璁板綍鐨� `itemCode` 鎴� `productCode` 浼犲叆 |
+| pageNo | Integer | 鍚� | 椤电爜 |
+| pageSize | Integer | 鍚� | 姣忛〉鏉℃暟 |
+
+### 褰掓。璇︽儏锛堝惈闄勪欢锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/mes/pd/archive/detail-by-product` | 鎸変骇鍝佺紪鐮佽幏鍙栧綊妗h鎯呭拰闄勪欢 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| productCode | String | 鏄� | 浜у搧缂栫爜 |
+
+**鍝嶅簲缁撴瀯锛�**
+
+```json
+{
+ "code": 0,
+ "data": [
+ {
+ "project": {
+ "id": 1,
+ "taskCode": "PD2024001",
+ "taskName": "鏂颁骇鍝佽璁′换鍔�",
+ "productCode": "P001",
+ "productName": "娉ㄥ浜у搧A",
+ "version": "V1.0",
+ "status": 4,
+ "chiefDesigner": 1,
+ "chiefDesignerNickname": "寮犱笁"
+ },
+ "archives": [
+ {
+ "id": 1,
+ "archiveCode": "ARC2024001",
+ "archiver": 1,
+ "archiverNickname": "鐜嬩簲",
+ "archiveStatus": 1,
+ "archiveTime": "2024-01-15T10:30:00"
+ }
+ ],
+ "documents": [
+ {
+ "id": 1,
+ "documentType": "1",
+ "documentName": "浜у搧璁捐鍥�",
+ "documentVersion": "V1.0",
+ "fileName": "design.pdf",
+ "attachmentList": [
+ {
+ "id": "blob-id-xxx",
+ "name": "璁捐鍥剧焊.pdf",
+ "url": "/system/storage-blob/preview/xxx"
+ }
+ ],
+ "uploadUserId": 1,
+ "uploadUserNickname": "璧靛叚",
+ "uploadTime": "2024-01-10T09:00:00"
+ }
+ ]
+ }
+ ]
+}
+```
+
+---
+
+## 鏁版嵁浼犻��
+
+鐢熶骇娴佺▼鍚勭幆鑺傜殑璁板綍涓凡鍖呭惈浜у搧缂栫爜锛岀洿鎺ヤ紶缁欏綊妗f帴鍙o細
+
+| 鏉ユ簮椤甸潰 | 瀛楁鍚� | 浼犵粰褰掓。鎺ュ彛 |
+|----------|--------|-------------|
+| 鐢熶骇宸ュ崟 | `productCode` | `?productCode={productCode}` |
+| 鐢熶骇浠诲姟 | `itemCode` | `?productCode={itemCode}` |
+| 鐢熶骇鎶ュ伐 | `itemCode` | `?productCode={itemCode}` |
+| 寰呮姤宸ュ彴璐� | `itemCode` | `?productCode={itemCode}` |
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鏌ョ湅褰掓。鎸夐挳
+
+鍦ㄧ敓浜у伐鍗曘�佷换鍔°�佹姤宸ュ垪琛ㄧ殑鎿嶄綔鍒楋紝浠ュ強璇︽儏椤碉紝鏂板"璁捐褰掓。"鎸夐挳銆�
+
+鐐瑰嚮鍚庡脊鍑烘娊灞夋垨寮圭獥锛岃皟鐢� `/mes/pd/archive/detail-by-product?productCode={code}` 鑾峰彇褰掓。璇︽儏銆�
+
+### 2. 寮圭獥鍐呭灞曠ず
+
+浠� Tab 鎴栧垎鍖哄睍绀轰笁閮ㄥ垎鍐呭锛�
+
+| 鍖哄煙 | 鍐呭 | 鏁版嵁鏉ユ簮 |
+|------|------|----------|
+| 椤圭洰淇℃伅 | 浠诲姟缂栫爜銆佷换鍔″悕绉般�佷骇鍝佺紪鐮�/鍚嶇О銆佺増鏈�佷富璁捐甯堛�佺姸鎬� | `response[].project` |
+| 褰掓。璁板綍 | 琛ㄦ牸锛氬綊妗g紪鍙枫�佸綊妗d汉銆佸綊妗g姸鎬併�佸綊妗f椂闂� | `response[].archives` |
+| 璁捐璧勬枡 | 琛ㄦ牸锛氳祫鏂欑被鍨嬨�佸悕绉般�佺増鏈�佹枃浠跺悕锛岄檮浠跺彲鐐瑰嚮涓嬭浇/棰勮 | `response[].documents` |
+
+### 3. 闄勪欢澶勭悊
+
+璁捐璧勬枡涓殑 `attachmentList` 涓洪檮浠跺垪琛紝鍏朵腑 `url` 鍙洿鎺ョ敤浜庢枃浠堕瑙�/涓嬭浇銆�
+
+鏂囦欢棰勮 URL 鏍煎紡锛�
+- 棰勮锛歚/system/storage-blob/preview/{fileName}`
+- 涓嬭浇锛歚/system/storage-blob/download/{fileName}`
+
+---
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 瑙勫垯 | 璇存槑 |
+|------|------|
+| 浜у搧鏈綊妗� | 鎺ュ彛杩斿洖绌烘暟缁勶紝寮圭獥鎻愮ず"璇ヤ骇鍝佹殏鏃犺璁″綊妗�" |
+| 澶氫釜椤圭洰 | 鍚屼竴浜у搧鍙兘鏈夊涓璁¢」鐩紝鎸夐」鐩垎缁勫睍绀� |
+| 闄勪欢鏉冮檺 | 闄勪欢棰勮/涓嬭浇璧� system 妯″潡 Storage API锛屾棤闇�棰濆閴存潈 |
+| 绌洪檮浠� | `attachmentList` 鍙兘涓虹┖鏁扮粍锛屽墠绔垽鏂悗涓嶆樉绀轰笅杞介摼鎺� |
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+- 鍒楄〃椤垫搷浣滃垪瀹藉害鏈夐檺锛屽缓璁寜閽枃瀛楃畝鍐欎负"褰掓。"
+- `detail-by-product` 鎺ュ彛杩斿洖鏁扮粍锛屼竴涓骇鍝佸彲鑳藉搴斿涓璁¢」鐩�
+- 闄勪欢棰勮浣跨敤 `window.open()` 鎵撳紑棰勮 URL
+- 鎺ュ彛宸插瓨鍦紝鍓嶇鐩存帴璋冪敤 `src/api/mes/pd/archive/index.ts` 涓殑灏佽鏂规硶
diff --git a/docs/pro_task_calculate_duration.md b/docs/pro_task_calculate_duration.md
new file mode 100644
index 0000000..b679ee1
--- /dev/null
+++ b/docs/pro_task_calculate_duration.md
@@ -0,0 +1,81 @@
+# 鐢熶骇浠诲姟 - 閫夊伐浣滅珯鑷姩璁$畻鐢熶骇鏃堕暱锛堝墠绔仈璋冩柟妗堬級
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 璺敱璺緞 | 璇存槑 |
+|------|----------|------|
+| 鐢熶骇浠诲姟琛ㄥ崟 | `/mes/pro-task` (鏂板/缂栬緫寮圭獥) | 閫夋嫨宸ヤ綔绔欏悗鑷姩璋冪敤鎺ュ彛璁$畻鐢熶骇鏃堕暱 |
+
+---
+
+## API
+
+### 鏂板鎺ュ彛锛氳绠楃敓浜ф椂闀�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/mes/pro/task/calculate-duration` | 鏍规嵁宸ヤ綔绔欏拰鏁伴噺璁$畻鐢熶骇鏃堕暱 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| workstationId | Long | 鏄� | 宸ヤ綔绔欑紪鍙� |
+| quantity | BigDecimal | 鏄� | 鎺掍骇鏁伴噺 |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+ "code": 0,
+ "data": {
+ "duration": 3
+ },
+ "msg": ""
+}
+```
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| duration | Integer | 鐢熶骇鏃堕暱锛堝皬鏃讹級锛屾棤缁戝畾璁惧鏃惰繑鍥� 0 |
+
+---
+
+## 鍓嶇淇敼鐐�
+
+### 1. 瑙﹀彂鏃舵満
+
+鍦ㄦ柊澧�/缂栬緫鐢熶骇浠诲姟鐨勮〃鍗曚腑锛屽綋鐢ㄦ埛**閫夋嫨宸ヤ綔绔�**鎴�**淇敼鎺掍骇鏁伴噺**鍚庯紝鑷姩璋冪敤璁$畻鎺ュ彛銆�
+
+### 2. 瀛楁鍥炲~
+
+鎺ュ彛杩斿洖鐨� `duration` 鍊艰嚜鍔ㄥ~鍏ヨ〃鍗曠殑"鐢熶骇鏃堕暱"瀛楁锛屾棤闇�鐢ㄦ埛鎵嬪姩璁$畻銆�
+
+### 3. 杈圭晫鎯呭喌
+
+| 鍦烘櫙 | 琛ㄧ幇 |
+|------|------|
+| 鏈�夊伐浣滅珯 | 涓嶈皟鐢ㄦ帴鍙� |
+| 鏈~鏁伴噺 | 涓嶈皟鐢ㄦ帴鍙� |
+| 宸ヤ綔绔欐湭缁戝畾璁惧 | 杩斿洖 `duration=0`锛屽墠绔彲鎻愮ず"璇ュ伐浣滅珯鏈粦瀹氳澶�" |
+| 璁惧鏈厤缃敓浜ф晥鐜� | 杩斿洖 `duration=0`锛屽墠绔彲鎻愮ず"缁戝畾璁惧鏈厤缃敓浜ф晥鐜�" |
+
+---
+
+## 璁$畻閫昏緫璇存槑
+
+| 姝ラ | 璇存槑 |
+|------|------|
+| 1 | 鏌ヨ宸ヤ綔绔欑粦瀹氱殑鎵�鏈夎澶� |
+| 2 | 鎬绘晥鐜� = 危(姣忓彴璁惧鐨� `productionEfficiency` 脳 `conveyorBelt`) |
+| 3 | 浼犻�佸饱甯︿负 0 鎴栫┖鏃堕粯璁ゅ彇 1 |
+| 4 | 鐢熶骇鏃堕暱锛堝皬鏃讹級= 鍚戜笂鍙栨暣(鎺掍骇鏁伴噺 / 鎬绘晥鐜�) |
+
+---
+
+## 娉ㄦ剰浜嬮」
+
+- 璋冪敤鎺ュ彛鍓嶉渶纭繚 `workstationId` 鍜� `quantity` 鍧囨湁鍊硷紝閬垮厤鏃犳晥璇锋眰
+- 鎺ュ彛杩斿洖 0 鏃讹紝寤鸿缁欏嚭鍙嬪ソ鎻愮ず锛岃�岄潪闈欓粯
+- 鐢ㄦ埛浠嶅彲鎵嬪姩淇敼璁$畻鍚庣殑鐢熶骇鏃堕暱
+- 璇ユ帴鍙d粎鐢ㄤ簬鍓嶇杈呭姪璁$畻锛屼笉褰卞搷鍚庣鍒涘缓/鏇存柊浠诲姟鐨勪笟鍔¢�昏緫
diff --git a/docs/sql/aftersales_init.sql b/docs/sql/aftersales_init.sql
new file mode 100644
index 0000000..536020c
--- /dev/null
+++ b/docs/sql/aftersales_init.sql
@@ -0,0 +1,170 @@
+-- ============================================================
+-- 鍞悗宸ュ崟妯″潡 DDL
+-- ============================================================
+
+-- 1. 鍞悗宸ュ崟涓昏〃
+DROP TABLE IF EXISTS `after_sale_ticket`;
+CREATE TABLE `after_sale_ticket` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '宸ュ崟缂栧彿',
+ `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '宸ュ崟鍗曞彿',
+ `name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '宸ュ崟涓婚/鏍囬',
+ `customer_id` bigint NOT NULL COMMENT '瀹㈡埛缂栧彿锛屽叧鑱� crm_customer.id',
+ `contact_id` bigint DEFAULT NULL COMMENT '鑱旂郴浜虹紪鍙凤紝鍏宠仈 crm_contact.id',
+ `sale_order_id` bigint DEFAULT NULL COMMENT '閿�鍞鍗曠紪鍙凤紝鍏宠仈 erp_sale_order.id',
+ `sale_order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '閿�鍞鍗曞彿(鍐椾綑)',
+ `contract_id` bigint DEFAULT NULL COMMENT '鍚堝悓缂栧彿锛屽叧鑱� crm_contract.id',
+ `issue_type` tinyint NOT NULL DEFAULT 0 COMMENT '闂绫诲瀷: 0-鏈垽瀹� 1-涓�鑸棶棰� 2-缁翠慨闂 3-閫�璐ч棶棰�',
+ `issue_category` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '闂鍒嗙被',
+ `issue_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '闂鎻忚堪',
+ `severity_level` tinyint DEFAULT 0 COMMENT '涓ラ噸绋嬪害: 0-杞诲井 1-涓�鑸� 2-涓ラ噸 3-鑷村懡',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��: 0-鑽夌 10-澶勭悊涓� 30-寰呴��璐� 40-寰呴��娆�/鎹㈣揣 50-宸插畬缁� 60-宸插叧闂� 99-宸插彇娑�',
+ `owner_user_id` bigint NOT NULL COMMENT '璐熻矗浜虹敤鎴风紪鍙�',
+ `handler_user_id` bigint DEFAULT NULL COMMENT '澶勭悊浜虹敤鎴风紪鍙�',
+ `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'BPM宸ヤ綔娴佸疄渚嬬紪鍙�',
+ `return_id` bigint DEFAULT NULL COMMENT '鍏宠仈閫�璐х敵璇风紪鍙�',
+ `repair_id` bigint DEFAULT NULL COMMENT '鍏宠仈缁翠慨璁板綍缂栧彿',
+ `resolved_time` datetime DEFAULT NULL COMMENT '瑙e喅鏃堕棿',
+ `closed_time` datetime DEFAULT NULL COMMENT '鍏抽棴鏃堕棿',
+ `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_customer_id` (`customer_id`),
+ KEY `idx_sale_order_id` (`sale_order_id`),
+ KEY `idx_owner_user_id` (`owner_user_id`),
+ KEY `idx_status` (`status`),
+ KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鍞悗宸ュ崟涓昏〃';
+
+-- 2. 鍞悗宸ュ崟鏄庣粏琛岃〃
+DROP TABLE IF EXISTS `after_sale_ticket_item`;
+CREATE TABLE `after_sale_ticket_item` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `ticket_id` bigint NOT NULL COMMENT '宸ュ崟缂栧彿锛屽叧鑱� after_sale_ticket.id',
+ `sale_order_item_id` bigint DEFAULT NULL COMMENT '閿�鍞鍗曡缂栧彿',
+ `item_id` bigint NOT NULL COMMENT 'MDM鐗╂枡缂栧彿',
+ `item_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '鐗╂枡缂栫爜(鍐椾綑)',
+ `item_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '鐗╂枡鍚嶇О(鍐椾綑)',
+ `batch_id` bigint DEFAULT NULL COMMENT '鐢熶骇鎵规缂栧彿',
+ `batch_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '鐢熶骇鎵规鍙�',
+ `sale_quantity` decimal(10,3) NOT NULL DEFAULT 0.000 COMMENT '閿�鍞暟閲�',
+ `return_quantity` decimal(10,3) DEFAULT 0.000 COMMENT '閫�璐ф暟閲�',
+ `need_repair` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁闇�瑕佺淮淇�',
+ `need_return` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁闇�瑕侀��璐�',
+ `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `idx_ticket_id` (`ticket_id`),
+ KEY `idx_item_id` (`item_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鍞悗宸ュ崟鏄庣粏琛岃〃';
+
+-- 3. 閫�璐х敵璇疯〃
+DROP TABLE IF EXISTS `after_sale_return`;
+CREATE TABLE `after_sale_return` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '閫�璐х敵璇风紪鍙�',
+ `no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '閫�璐у崟鍙�',
+ `ticket_id` bigint NOT NULL COMMENT '鍏宠仈宸ュ崟缂栧彿锛屽叧鑱� after_sale_ticket.id',
+ `customer_id` bigint NOT NULL COMMENT '瀹㈡埛缂栧彿',
+ `sale_order_id` bigint DEFAULT NULL COMMENT '鍏宠仈閿�鍞鍗曠紪鍙�',
+ `return_type` tinyint NOT NULL COMMENT '閫�璐х被鍨�: 1-閫�娆鹃��璐� 2-鎹㈣揣 3-浠呴��娆�',
+ `return_reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '閫�璐у師鍥�',
+ `defect_category` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '缂洪櫡鍒嗙被',
+ `total_return_price` decimal(12,2) NOT NULL DEFAULT 0.00 COMMENT '搴旈��鎬婚噾棰�',
+ `actual_refund_price` decimal(12,2) DEFAULT 0.00 COMMENT '瀹為��閲戦',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��: 0-鑽夌 10-閿�鍞鏍镐腑 20-瀹℃牳閫氳繃宸插悓姝ES 30-妫�楠屼腑 40-妫�楠屽畬鎴� 50-宸插叆搴� 60-閫�娆�/鎹㈣揣瀹屾垚 70-宸插叧闂� 99-宸插彇娑�',
+ `audit_user_id` bigint DEFAULT NULL COMMENT '瀹℃牳浜�',
+ `audit_time` datetime DEFAULT NULL COMMENT '瀹℃牳鏃堕棿',
+ `mes_return_id` bigint DEFAULT NULL COMMENT 'MES閫�璐у崟缂栧彿锛屽叧鑱� mes_wm_return_sales.id',
+ `mes_return_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'MES閫�璐у崟鍙�(鍐椾綑)',
+ `quality_result` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '璐ㄦ缁撹: 鍚堟牸/鐗归噰/閫�璐�/鎶ュ簾',
+ `quality_report_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '璐ㄦ鎶ュ憡闄勪欢URL',
+ `return_inbound_time` datetime DEFAULT NULL COMMENT '閫�璐у叆搴撳畬鎴愭椂闂�',
+ `refund_time` datetime DEFAULT NULL COMMENT '閫�娆�/鎹㈣揣瀹屾垚鏃堕棿',
+ `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE KEY `uk_no` (`no`),
+ KEY `idx_ticket_id` (`ticket_id`),
+ KEY `idx_mes_return_id` (`mes_return_id`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鍞悗閫�璐х敵璇疯〃';
+
+-- 4. 閫�璐ф槑缁嗚琛�
+DROP TABLE IF EXISTS `after_sale_return_item`;
+CREATE TABLE `after_sale_return_item` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `return_id` bigint NOT NULL COMMENT '閫�璐х敵璇风紪鍙凤紝鍏宠仈 after_sale_return.id',
+ `ticket_item_id` bigint NOT NULL COMMENT '宸ュ崟鏄庣粏琛岀紪鍙凤紝鍏宠仈 after_sale_ticket_item.id',
+ `item_id` bigint NOT NULL COMMENT 'MDM鐗╂枡缂栧彿',
+ `item_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '鐗╂枡缂栫爜(鍐椾綑)',
+ `item_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '鐗╂枡鍚嶇О(鍐椾綑)',
+ `batch_id` bigint DEFAULT NULL COMMENT '鎵规缂栧彿',
+ `batch_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '鎵规鍙�',
+ `return_quantity` decimal(10,3) NOT NULL COMMENT '閫�璐ф暟閲�',
+ `unit_price` decimal(12,2) NOT NULL COMMENT '閿�鍞崟浠�',
+ `return_price` decimal(12,2) NOT NULL COMMENT '閫�璐ч噾棰�',
+ `mes_return_line_id` bigint DEFAULT NULL COMMENT 'MES閫�璐у崟琛岀紪鍙�',
+ `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `idx_return_id` (`return_id`),
+ KEY `idx_ticket_item_id` (`ticket_item_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鍞悗閫�璐ф槑缁嗚琛�';
+
+-- 5. 缁翠慨璁板綍琛�
+DROP TABLE IF EXISTS `after_sale_repair`;
+CREATE TABLE `after_sale_repair` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缁翠慨璁板綍缂栧彿',
+ `ticket_id` bigint NOT NULL COMMENT '鍏宠仈宸ュ崟缂栧彿锛屽叧鑱� after_sale_ticket.id',
+ `repair_type` tinyint NOT NULL COMMENT '缁翠慨绫诲瀷: 1-鐜板満缁翠慨 2-杩斿巶缁翠慨 3-杩滅▼鎸囧',
+ `fault_description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '鏁呴殰鎻忚堪',
+ `repair_action` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '缁翠慨鎺柦',
+ `repair_result` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '缁翠慨缁撴灉: 宸蹭慨澶�/鏃犳硶淇/閮ㄥ垎淇',
+ `repair_cost` decimal(10,2) DEFAULT 0.00 COMMENT '缁翠慨璐圭敤',
+ `repair_user_id` bigint DEFAULT NULL COMMENT '缁翠慨浜�',
+ `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵��: 0-寰呭鐞� 10-缁翠慨涓� 20-宸插畬鎴� 30-宸插叧闂�',
+ `start_time` datetime DEFAULT NULL COMMENT '寮�濮嬬淮淇椂闂�',
+ `finish_time` datetime DEFAULT NULL COMMENT '瀹屾垚缁翠慨鏃堕棿',
+ `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '澶囨敞',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `idx_ticket_id` (`ticket_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鍞悗缁翠慨璁板綍琛�';
+
+-- 6. 鍞悗鏁版嵁鏉冮檺琛�
+DROP TABLE IF EXISTS `after_sale_permission`;
+CREATE TABLE `after_sale_permission` (
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '缂栧彿',
+ `biz_type` tinyint NOT NULL COMMENT '鏁版嵁绫诲瀷: 1-鍞悗宸ュ崟 2-閫�璐х敵璇� 3-缁翠慨璁板綍',
+ `biz_id` bigint NOT NULL COMMENT '鏁版嵁缂栧彿',
+ `user_id` bigint NOT NULL COMMENT '鐢ㄦ埛缂栧彿',
+ `level` tinyint NOT NULL COMMENT '鏉冮檺绾у埆: 1-璐熻矗浜� 2-鍙 3-璇诲啓',
+ `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
+ `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+ `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鏇存柊鑰�',
+ `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+ `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `idx_biz` (`biz_type`, `biz_id`),
+ KEY `idx_user` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='鍞悗鏁版嵁鏉冮檺琛�';
diff --git a/pom.xml b/pom.xml
index 930f493..72800d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,9 @@
<!-- SRM 渚涘簲鍟嗗叧绯荤鐞嗘ā鍧� -->
<module>yudao-module-srm-api</module>
<module>yudao-module-srm</module>
+ <!-- 鍞悗宸ュ崟妯″潡 -->
+ <module>yudao-module-aftersales-api</module>
+ <module>yudao-module-aftersales</module>
<!-- 璇峰弬鑰� https://doc.iocoder.cn/ai/build/ 鏂囨。锛屽畬鎴� AI 妯″潡鐨勫惎鍔紒锛侊紒 -->
<!-- <module>yudao-module-ai</module>-->
</modules>
diff --git a/sql/mysql/mes_pd_menu_init.sql b/sql/mysql/mes_pd_menu_init.sql
new file mode 100644
index 0000000..426e3d6
--- /dev/null
+++ b/sql/mysql/mes_pd_menu_init.sql
@@ -0,0 +1,37 @@
+-- =============================================
+-- MES PD 浜у搧璁捐妯″潡鑿滃崟鍒濆鍖�
+-- 鍦� MES 绯荤粺(5100) 涓嬫柊澧�"浜у搧璁捐"浜岀骇鐩綍
+-- 璁捐椤圭洰銆佽璁″綊妗d袱涓〉闈�
+-- =============================================
+
+-- 淇鍘熸湁"浜у搧璁捐"(6809) 鍛藉悕涓�"宸ヨ壓璁捐"锛岄伩鍏嶅啿绐侊紙鍏跺唴瀹逛负宸ヨ壓璁捐锛氬伐搴忚缃�佸伐鑹烘祦绋嬬瓑锛�
+UPDATE `system_menu` SET `name` = '宸ヨ壓璁捐', `updater` = '1', `update_time` = NOW() WHERE `id` = 6809 AND `name` = '浜у搧璁捐';
+
+-- 浜у搧璁捐锛堜簩绾х洰褰曪紝鏀惧湪 MES 绯荤粺涓嬶紝sort=25 浣嶄簬宸ヨ壓璁捐涔嬪悗锛�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (602571, '浜у搧璁捐', '', 1, 25, 5100, 'pd', 'ep:design', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 璁捐椤圭洰锛堜笁绾ц彍鍗曪級
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (602572, '璁捐椤圭洰', '', 2, 1, 602571, 'project', 'ep:document', 'mes/pd/project/index', 'MesPdProject', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 璁捐椤圭洰 - 鎸夐挳鏉冮檺
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+(602573, '璁捐椤圭洰鏌ヨ', 'mes:pd-project:query', 3, 1, 602572, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602574, '璁捐椤圭洰鍒涘缓', 'mes:pd-project:create', 3, 2, 602572, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602575, '璁捐椤圭洰鏇存柊', 'mes:pd-project:update', 3, 3, 602572, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602576, '璁捐椤圭洰鍒犻櫎', 'mes:pd-project:delete', 3, 4, 602572, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602577, '璁捐椤圭洰瀵煎嚭', 'mes:pd-project:export', 3, 5, 602572, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 璁捐褰掓。锛堜笁绾ц彍鍗曪級
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (602578, '璁捐褰掓。', '', 2, 2, 602571, 'archive', 'ep:folder', 'mes/pd/archive/index', 'MesPdArchive', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 璁捐褰掓。 - 鎸夐挳鏉冮檺
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+(602579, '璁捐褰掓。鏌ヨ', 'mes:pd-archive:query', 3, 1, 602578, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602580, '璁捐褰掓。鏇存柊', 'mes:pd-archive:update', 3, 2, 602578, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602581, '璁捐褰掓。鍒犻櫎', 'mes:pd-archive:delete', 3, 3, 602578, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(602582, '璁捐褰掓。瀵煎嚭', 'mes:pd-archive:export', 3, 4, 602578, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
diff --git a/yudao-module-aftersales-api/pom.xml b/yudao-module-aftersales-api/pom.xml
new file mode 100644
index 0000000..d1a3ca3
--- /dev/null
+++ b/yudao-module-aftersales-api/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-aftersales-api</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ 鍞悗宸ュ崟妯″潡鐨� API 鎺ュ彛瀹氫箟锛屼緵鍏朵粬妯″潡寮曠敤
+ </description>
+
+ <dependencies>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-common</artifactId>
+ </dependency>
+
+ <!-- Swagger 娉ㄨВ -->
+ <dependency>
+ <groupId>org.springdoc</groupId>
+ <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApi.java b/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApi.java
new file mode 100644
index 0000000..f3567d7
--- /dev/null
+++ b/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApi.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.aftersales.api;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.aftersales.api.dto.AfterSaleReturnRespDTO;
+import cn.iocoder.yudao.module.aftersales.api.dto.AfterSaleTicketRespDTO;
+
+import java.math.BigDecimal;
+
+/**
+ * 鍞悗宸ュ崟 API 鎺ュ彛锛堜緵 MES/ERP 璺ㄦā鍧楄皟鐢級
+ */
+public interface AfterSaleTicketApi {
+
+ String PREFIX = "/rpc-api/aftersales/ticket";
+
+ /**
+ * MES 閫�璐ц川妫�瀹屾垚鍚庡洖璋�
+ *
+ * @param returnId 閫�璐х敵璇风紪鍙�
+ * @param qualityResult 璐ㄦ缁撹
+ * @param reportUrl 璐ㄦ鎶ュ憡 URL
+ */
+ CommonResult<Boolean> onReturnQualityFinished(Long returnId, String qualityResult, String reportUrl);
+
+ /**
+ * MES 閫�璐у叆搴撳畬鎴愬悗鍥炶皟
+ *
+ * @param returnId 閫�璐х敵璇风紪鍙�
+ */
+ CommonResult<Boolean> onReturnInboundFinished(Long returnId);
+
+ /**
+ * ERP 閫�娆惧畬鎴愬悗鍥炶皟
+ *
+ * @param returnId 閫�璐х敵璇风紪鍙�
+ * @param actualRefundPrice 瀹為��閲戦
+ */
+ CommonResult<Boolean> onRefundCompleted(Long returnId, BigDecimal actualRefundPrice);
+
+ /**
+ * 鏌ヨ鍞悗宸ュ崟锛堜緵 MES/ERP 鏌ヨ鍏宠仈淇℃伅锛�
+ *
+ * @param id 宸ュ崟缂栧彿
+ */
+ CommonResult<AfterSaleTicketRespDTO> getTicket(Long id);
+
+ /**
+ * 鏌ヨ閫�璐х敵璇凤紙渚� MES/ERP 鏌ヨ鍏宠仈淇℃伅锛�
+ *
+ * @param id 閫�璐х敵璇风紪鍙�
+ */
+ CommonResult<AfterSaleReturnRespDTO> getReturn(Long id);
+
+}
diff --git a/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleReturnRespDTO.java b/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleReturnRespDTO.java
new file mode 100644
index 0000000..7afc84f
--- /dev/null
+++ b/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleReturnRespDTO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.aftersales.api.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 鍞悗閫�璐х敵璇峰搷搴� DTO锛堜緵 RPC 璋冪敤浣跨敤锛�
+ */
+@Data
+public class AfterSaleReturnRespDTO {
+
+ private Long id;
+ private String no;
+ private Long ticketId;
+ private Long customerId;
+ private Long saleOrderId;
+ private Integer returnType;
+ private String returnReason;
+ private BigDecimal totalReturnPrice;
+ private BigDecimal actualRefundPrice;
+ private Integer status;
+ private Long mesReturnId;
+ private String mesReturnCode;
+ private String qualityResult;
+ private String qualityReportUrl;
+ private LocalDateTime returnInboundTime;
+ private LocalDateTime refundTime;
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleTicketRespDTO.java b/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleTicketRespDTO.java
new file mode 100644
index 0000000..971339c
--- /dev/null
+++ b/yudao-module-aftersales-api/src/main/java/cn/iocoder/yudao/module/aftersales/api/dto/AfterSaleTicketRespDTO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.aftersales.api.dto;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鍞悗宸ュ崟鍝嶅簲 DTO锛堜緵 RPC 璋冪敤浣跨敤锛�
+ */
+@Data
+public class AfterSaleTicketRespDTO {
+
+ private Long id;
+ private String no;
+ private String name;
+ private Long customerId;
+ private Long saleOrderId;
+ private String saleOrderNo;
+ private Integer issueType;
+ private String issueCategory;
+ private String issueDescription;
+ private Integer severityLevel;
+ private Integer status;
+ private Long ownerUserId;
+ private Long handlerUserId;
+ private Long returnId;
+ private Long repairId;
+ private LocalDateTime resolvedTime;
+ private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-aftersales/pom.xml b/yudao-module-aftersales/pom.xml
new file mode 100644
index 0000000..ab92efd
--- /dev/null
+++ b/yudao-module-aftersales/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao</artifactId>
+ <version>${revision}</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>yudao-module-aftersales</artifactId>
+ <packaging>jar</packaging>
+
+ <name>${project.artifactId}</name>
+ <description>
+ aftersales 鍖呬笅锛屽敭鍚庢湇鍔$鐞嗭紙After-sales Service Management锛夈��
+ 渚嬪璇达細鍞悗宸ュ崟銆侀��璐х敵璇枫�佺淮淇褰曠瓑绛�
+ </description>
+
+ <dependencies>
+ <!-- 鍞悗 API 妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-aftersales-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- CRM API 妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-crm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-system</artifactId>
+ <version>${revision}</version>
+ </dependency>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-infra</artifactId>
+ <version>${revision}</version>
+ </dependency>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-bpm</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- MDM 涓绘暟鎹鐞� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mdm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- ERP 閿�鍞鍗� API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-erp-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- MES API -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-mes-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- HRM API锛堟暟鎹氦鎺ワ級 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-hrm-api</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
+ <!-- 涓氬姟缁勪欢 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
+ </dependency>
+
+ <!-- Web 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-web</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-security</artifactId>
+ </dependency>
+
+ <!-- DB 鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
+ </dependency>
+
+ <!-- 宸ュ叿绫荤浉鍏� -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-excel</artifactId>
+ </dependency>
+
+ <!-- Test 娴嬭瘯鐩稿叧 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-spring-boot-starter-test</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApiImpl.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApiImpl.java
new file mode 100644
index 0000000..d636f28
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/api/AfterSaleTicketApiImpl.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.aftersales.api;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.api.dto.AfterSaleReturnRespDTO;
+import cn.iocoder.yudao.module.aftersales.api.dto.AfterSaleTicketRespDTO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO;
+import cn.iocoder.yudao.module.aftersales.service.returnobj.AfterSaleReturnService;
+import cn.iocoder.yudao.module.aftersales.service.ticket.AfterSaleTicketService;
+import jakarta.annotation.Resource;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.math.BigDecimal;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+/**
+ * 鍞悗宸ュ崟 API 瀹炵幇绫伙紙渚� MES/ERP 璺ㄦā鍧楄皟鐢級
+ */
+@RestController
+public class AfterSaleTicketApiImpl implements AfterSaleTicketApi {
+
+ @Resource
+ private AfterSaleTicketService ticketService;
+ @Resource
+ private AfterSaleReturnService returnService;
+
+ @Override
+ public CommonResult<Boolean> onReturnQualityFinished(Long returnId, String qualityResult, String reportUrl) {
+ returnService.onQualityFinished(returnId, qualityResult, reportUrl);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult<Boolean> onReturnInboundFinished(Long returnId) {
+ returnService.onInboundFinished(returnId);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult<Boolean> onRefundCompleted(Long returnId, BigDecimal actualRefundPrice) {
+ returnService.onRefundCompleted(returnId, actualRefundPrice);
+ return success(true);
+ }
+
+ @Override
+ public CommonResult<AfterSaleTicketRespDTO> getTicket(Long id) {
+ AfterSaleTicketDO ticket = ticketService.getTicket(id);
+ return success(BeanUtils.toBean(ticket, AfterSaleTicketRespDTO.class));
+ }
+
+ @Override
+ public CommonResult<AfterSaleReturnRespDTO> getReturn(Long id) {
+ AfterSaleReturnDO returnDO = returnService.getReturn(id);
+ return success(BeanUtils.toBean(returnDO, AfterSaleReturnRespDTO.class));
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/AfterSaleReturnController.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/AfterSaleReturnController.java
new file mode 100644
index 0000000..4f593cf
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/AfterSaleReturnController.java
@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.returnobj;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnRespVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnItemDO;
+import cn.iocoder.yudao.module.aftersales.service.returnobj.AfterSaleReturnService;
+import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍞悗閫�璐х敵璇�")
+@RestController
+@RequestMapping("/aftersales/return")
+@Validated
+public class AfterSaleReturnController {
+
+ @Resource
+ private AfterSaleReturnService returnService;
+
+ @Resource
+ private CrmCustomerApi customerApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓閫�璐х敵璇�")
+ @PreAuthorize("@ss.hasPermission('aftersales:return:create')")
+ public CommonResult<Long> createReturn(@Valid @RequestBody AfterSaleReturnSaveReqVO createReqVO) {
+ return success(returnService.createReturn(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/approve")
+ @Operation(summary = "瀹℃牳閫氳繃閫�璐х敵璇�")
+ @PreAuthorize("@ss.hasPermission('aftersales:return:approve')")
+ public CommonResult<Boolean> approveReturn(@RequestParam("id") Long id) {
+ returnService.approveReturn(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱閫�璐х敵璇�")
+ @Parameter(name = "id", description = "閫�璐х敵璇风紪鍙�", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('aftersales:return:query')")
+ public CommonResult<AfterSaleReturnRespVO> getReturn(@RequestParam("id") Long id) {
+ AfterSaleReturnDO returnDO = returnService.getReturn(id);
+ List<AfterSaleReturnItemDO> items = returnService.getReturnItemListByReturnId(id);
+ List<AfterSaleReturnRespVO> enrichedList = buildReturnDetailList(List.of(returnDO));
+ AfterSaleReturnRespVO respVO = enrichedList.get(0);
+ respVO.setItems(BeanUtils.toBean(items, AfterSaleReturnRespVO.Item.class));
+ return success(respVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱閫�璐х敵璇峰垎椤�")
+ @PreAuthorize("@ss.hasPermission('aftersales:return:query')")
+ public CommonResult<PageResult<AfterSaleReturnRespVO>> getReturnPage(@Valid AfterSaleReturnPageReqVO pageReqVO) {
+ PageResult<AfterSaleReturnDO> pageResult = returnService.getReturnPage(pageReqVO, getLoginUserId());
+ List<AfterSaleReturnRespVO> list = buildReturnDetailList(pageResult.getList());
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ // ========== 鍐呴儴鏂规硶锛氭壒閲忔嫾瑁呭叧鑱斿悕绉� ==========
+
+ private List<AfterSaleReturnRespVO> buildReturnDetailList(List<AfterSaleReturnDO> returnList) {
+ if (CollUtil.isEmpty(returnList)) {
+ return Collections.emptyList();
+ }
+ // 鎵归噺鍔犺浇瀹㈡埛鍚嶇О
+ Set<Long> customerIds = convertSet(returnList, AfterSaleReturnDO::getCustomerId);
+ Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap(customerIds);
+ // 鎷艰鍚嶇О瀛楁
+ return BeanUtils.toBean(returnList, AfterSaleReturnRespVO.class, vo -> {
+ CrmCustomerRespDTO customer = customerMap.get(vo.getCustomerId());
+ if (customer != null) vo.setCustomerName(customer.getName());
+ });
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnPageReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnPageReqVO.java
new file mode 100644
index 0000000..32340a0
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnPageReqVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * 鍞悗閫�璐х敵璇峰垎椤垫煡璇� VO
+ */
+@Schema(description = "鍞悗閫�璐х敵璇� - 鍒嗛〉鏌ヨ Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AfterSaleReturnPageReqVO extends PageParam {
+
+ @Schema(description = "閫�璐у崟鍙�", example = "RT20240101000001")
+ private String no;
+
+ @Schema(description = "鍏宠仈宸ュ崟缂栧彿", example = "1")
+ private Long ticketId;
+
+ @Schema(description = "瀹㈡埛缂栧彿", example = "1")
+ private Long customerId;
+
+ @Schema(description = "閫�璐х姸鎬�", example = "10")
+ private Integer status;
+
+ @Schema(description = "鍦烘櫙绫诲瀷", example = "1")
+ private Integer sceneType;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnRespVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnRespVO.java
new file mode 100644
index 0000000..007ef50
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnRespVO.java
@@ -0,0 +1,139 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 鍞悗閫�璐х敵璇峰搷搴� VO
+ */
+@Schema(description = "鍞悗閫�璐х敵璇� - 鍝嶅簲 VO")
+@Data
+public class AfterSaleReturnRespVO {
+
+ @Schema(description = "閫�璐х敵璇风紪鍙�", example = "1")
+ private Long id;
+
+ @Schema(description = "閫�璐у崟鍙�", example = "RT20240101000001")
+ private String no;
+
+ @Schema(description = "鍏宠仈宸ュ崟缂栧彿", example = "1")
+ private Long ticketId;
+
+ @Schema(description = "瀹㈡埛缂栧彿", example = "1")
+ private Long customerId;
+
+ @Schema(description = "瀹㈡埛鍚嶇О")
+ private String customerName;
+
+ @Schema(description = "鍏宠仈閿�鍞鍗曠紪鍙�", example = "1")
+ private Long saleOrderId;
+
+ @Schema(description = "閫�璐х被鍨�: 1-閫�娆鹃��璐� 2-鎹㈣揣 3-浠呴��娆�", example = "1")
+ private Integer returnType;
+
+ @Schema(description = "閫�璐у師鍥�", example = "浜у搧澶栬缂洪櫡")
+ private String returnReason;
+
+ @Schema(description = "缂洪櫡鍒嗙被", example = "澶栬缂洪櫡")
+ private String defectCategory;
+
+ @Schema(description = "搴旈��鎬婚噾棰�", example = "1000.00")
+ private BigDecimal totalReturnPrice;
+
+ @Schema(description = "瀹為��閲戦", example = "950.00")
+ private BigDecimal actualRefundPrice;
+
+ @Schema(description = "鐘舵��", example = "10")
+ private Integer status;
+
+ @Schema(description = "瀹℃牳浜�", example = "1")
+ private Long auditUserId;
+
+ @Schema(description = "瀹℃牳鏃堕棿")
+ private LocalDateTime auditTime;
+
+ @Schema(description = "MES閫�璐у崟缂栧彿", example = "1")
+ private Long mesReturnId;
+
+ @Schema(description = "MES閫�璐у崟鍙�", example = "MES-RT-001")
+ private String mesReturnCode;
+
+ @Schema(description = "璐ㄦ缁撹", example = "鍚堟牸")
+ private String qualityResult;
+
+ @Schema(description = "璐ㄦ鎶ュ憡闄勪欢URL")
+ private String qualityReportUrl;
+
+ @Schema(description = "閫�璐у叆搴撳畬鎴愭椂闂�")
+ private LocalDateTime returnInboundTime;
+
+ @Schema(description = "閫�娆�/鎹㈣揣瀹屾垚鏃堕棿")
+ private LocalDateTime refundTime;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鑰�")
+ private String creator;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private LocalDateTime createTime;
+
+ @Schema(description = "鏇存柊鑰�")
+ private String updater;
+
+ @Schema(description = "鏇存柊鏃堕棿")
+ private LocalDateTime updateTime;
+
+ @Schema(description = "閫�璐ф槑缁嗚鍒楄〃")
+ private List<Item> items;
+
+ @Schema(description = "閫�璐ф槑缁嗚")
+ @Data
+ public static class Item {
+
+ @Schema(description = "缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "閫�璐х敵璇风紪鍙�", example = "1")
+ private Long returnId;
+
+ @Schema(description = "宸ュ崟鏄庣粏琛岀紪鍙�", example = "1")
+ private Long ticketItemId;
+
+ @Schema(description = "MDM鐗╂枡缂栧彿", example = "1")
+ private Long itemId;
+
+ @Schema(description = "鐗╂枡缂栫爜", example = "MAT-001")
+ private String itemCode;
+
+ @Schema(description = "鐗╂枡鍚嶇О", example = "鐢靛瓙鍏冨櫒浠�")
+ private String itemName;
+
+ @Schema(description = "鎵规缂栧彿", example = "1")
+ private Long batchId;
+
+ @Schema(description = "鎵规鍙�", example = "BATCH-2024-001")
+ private String batchCode;
+
+ @Schema(description = "閫�璐ф暟閲�", example = "10.000")
+ private BigDecimal returnQuantity;
+
+ @Schema(description = "閿�鍞崟浠�", example = "100.00")
+ private BigDecimal unitPrice;
+
+ @Schema(description = "閫�璐ч噾棰�", example = "1000.00")
+ private BigDecimal returnPrice;
+
+ @Schema(description = "MES閫�璐у崟琛岀紪鍙�", example = "1")
+ private Long mesReturnLineId;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnSaveReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnSaveReqVO.java
new file mode 100644
index 0000000..6ff52ca
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/returnobj/vo/AfterSaleReturnSaveReqVO.java
@@ -0,0 +1,88 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 鍞悗閫�璐х敵璇蜂繚瀛� VO
+ */
+@Schema(description = "鍞悗閫�璐х敵璇� - 淇濆瓨 Request VO")
+@Data
+public class AfterSaleReturnSaveReqVO {
+
+ @Schema(description = "閫�璐х敵璇风紪鍙�", example = "1")
+ private Long id;
+
+ @Schema(description = "鍏宠仈宸ュ崟缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "鍏宠仈宸ュ崟涓嶈兘涓虹┖")
+ private Long ticketId;
+
+ @Schema(description = "瀹㈡埛缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "瀹㈡埛涓嶈兘涓虹┖")
+ private Long customerId;
+
+ @Schema(description = "鍏宠仈閿�鍞鍗曠紪鍙�", example = "1")
+ private Long saleOrderId;
+
+ @Schema(description = "閫�璐х被鍨�: 1-閫�娆鹃��璐� 2-鎹㈣揣 3-浠呴��娆�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "閫�璐х被鍨嬩笉鑳戒负绌�")
+ private Integer returnType;
+
+ @Schema(description = "閫�璐у師鍥�", requiredMode = Schema.RequiredMode.REQUIRED, example = "浜у搧澶栬缂洪櫡")
+ @NotEmpty(message = "閫�璐у師鍥犱笉鑳戒负绌�")
+ private String returnReason;
+
+ @Schema(description = "缂洪櫡鍒嗙被", example = "澶栬缂洪櫡")
+ private String defectCategory;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+ @Schema(description = "閫�璐ф槑缁嗚鍒楄〃")
+ private List<Item> items;
+
+ @Schema(description = "閫�璐ф槑缁嗚")
+ @Data
+ public static class Item {
+
+ @Schema(description = "宸ュ崟鏄庣粏琛岀紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "宸ュ崟鏄庣粏琛屼笉鑳戒负绌�")
+ private Long ticketItemId;
+
+ @Schema(description = "MDM鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐗╂枡涓嶈兘涓虹┖")
+ private Long itemId;
+
+ @Schema(description = "鐗╂枡缂栫爜", example = "MAT-001")
+ private String itemCode;
+
+ @Schema(description = "鐗╂枡鍚嶇О", example = "鐢靛瓙鍏冨櫒浠�")
+ private String itemName;
+
+ @Schema(description = "鎵规缂栧彿", example = "1")
+ private Long batchId;
+
+ @Schema(description = "鎵规鍙�", example = "BATCH-2024-001")
+ private String batchCode;
+
+ @Schema(description = "閫�璐ф暟閲�", requiredMode = Schema.RequiredMode.REQUIRED, example = "10.000")
+ @NotNull(message = "閫�璐ф暟閲忎笉鑳戒负绌�")
+ private BigDecimal returnQuantity;
+
+ @Schema(description = "閿�鍞崟浠�", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
+ @NotNull(message = "閿�鍞崟浠蜂笉鑳戒负绌�")
+ private BigDecimal unitPrice;
+
+ @Schema(description = "閫�璐ч噾棰�", example = "1000.00")
+ private BigDecimal returnPrice;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java
new file mode 100644
index 0000000..3acf172
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java
@@ -0,0 +1,165 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.ticket;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketRespVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO;
+import cn.iocoder.yudao.module.aftersales.service.ticket.AfterSaleTicketService;
+import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍞悗宸ュ崟")
+@RestController
+@RequestMapping("/aftersales/ticket")
+@Validated
+public class AfterSaleTicketController {
+
+ @Resource
+ private AfterSaleTicketService ticketService;
+
+ @Resource
+ private CrmCustomerApi customerApi;
+ @Resource
+ private AdminUserApi adminUserApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "鍒涘缓鍞悗宸ュ崟")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:create')")
+ public CommonResult<Long> createTicket(@Valid @RequestBody AfterSaleTicketSaveReqVO createReqVO) {
+ return success(ticketService.createTicket(createReqVO, getLoginUserId()));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊鍞悗宸ュ崟")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:update')")
+ public CommonResult<Boolean> updateTicket(@Valid @RequestBody AfterSaleTicketSaveReqVO updateReqVO) {
+ ticketService.updateTicket(updateReqVO);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎鍞悗宸ュ崟")
+ @Parameter(name = "id", description = "宸ュ崟缂栧彿", required = true)
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:delete')")
+ public CommonResult<Boolean> deleteTicket(@RequestParam("id") Long id) {
+ ticketService.deleteTicket(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "鑾峰緱鍞悗宸ュ崟")
+ @Parameter(name = "id", description = "宸ュ崟缂栧彿", required = true, example = "1")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:query')")
+ public CommonResult<AfterSaleTicketRespVO> getTicket(@RequestParam("id") Long id) {
+ AfterSaleTicketDO ticket = ticketService.getTicket(id);
+ List<AfterSaleTicketItemDO> items = ticketService.getTicketItemListByTicketId(id);
+ // 鎵归噺鍔犺浇鍏宠仈鍚嶇О
+ List<AfterSaleTicketRespVO> enrichedList = buildTicketDetailList(List.of(ticket));
+ AfterSaleTicketRespVO respVO = enrichedList.get(0);
+ respVO.setItems(BeanUtils.toBean(items, AfterSaleTicketRespVO.Item.class));
+ return success(respVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "鑾峰緱鍞悗宸ュ崟鍒嗛〉")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:query')")
+ public CommonResult<PageResult<AfterSaleTicketRespVO>> getTicketPage(@Valid AfterSaleTicketPageReqVO pageReqVO) {
+ PageResult<AfterSaleTicketDO> pageResult = ticketService.getTicketPage(pageReqVO, getLoginUserId());
+ List<AfterSaleTicketRespVO> list = buildTicketDetailList(pageResult.getList());
+ return success(new PageResult<>(list, pageResult.getTotal()));
+ }
+
+ @PostMapping("/assign")
+ @Operation(summary = "鍒嗛厤澶勭悊浜�")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:update')")
+ public CommonResult<Boolean> assignTicket(@RequestParam("id") Long id,
+ @RequestParam("handlerUserId") Long handlerUserId) {
+ ticketService.assignTicket(id, handlerUserId);
+ return success(true);
+ }
+
+ @PostMapping("/classify")
+ @Operation(summary = "闂鍒ゅ畾")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:update')")
+ public CommonResult<Boolean> classifyTicket(@RequestParam("id") Long id,
+ @RequestParam("issueType") Integer issueType,
+ @RequestParam(value = "issueCategory", required = false) String issueCategory,
+ @RequestParam(value = "severityLevel", required = false) Integer severityLevel) {
+ ticketService.classifyTicket(id, issueType, issueCategory, severityLevel);
+ return success(true);
+ }
+
+ @PutMapping("/resolve")
+ @Operation(summary = "瀹岀粨宸ュ崟")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:update')")
+ public CommonResult<Boolean> resolveTicket(@RequestParam("id") Long id) {
+ ticketService.resolveTicket(id);
+ return success(true);
+ }
+
+ @PutMapping("/close")
+ @Operation(summary = "鍏抽棴宸ュ崟")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:update')")
+ public CommonResult<Boolean> closeTicket(@RequestParam("id") Long id) {
+ ticketService.closeTicket(id);
+ return success(true);
+ }
+
+ @PutMapping("/cancel")
+ @Operation(summary = "鍙栨秷宸ュ崟")
+ @PreAuthorize("@ss.hasPermission('aftersales:ticket:update')")
+ public CommonResult<Boolean> cancelTicket(@RequestParam("id") Long id) {
+ ticketService.cancelTicket(id);
+ return success(true);
+ }
+
+ // ========== 鍐呴儴鏂规硶锛氭壒閲忔嫾瑁呭叧鑱斿悕绉� ==========
+
+ private List<AfterSaleTicketRespVO> buildTicketDetailList(List<AfterSaleTicketDO> ticketList) {
+ if (CollUtil.isEmpty(ticketList)) {
+ return Collections.emptyList();
+ }
+ // 1. 鎵归噺鍔犺浇瀹㈡埛鍚嶇О
+ Set<Long> customerIds = convertSet(ticketList, AfterSaleTicketDO::getCustomerId);
+ Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap(customerIds);
+ // 2. 鎵归噺鍔犺浇鐢ㄦ埛鍚嶇О锛堣礋璐d汉 + 澶勭悊浜猴級
+ Set<Long> userIds = new HashSet<>();
+ ticketList.forEach(t -> {
+ if (t.getOwnerUserId() != null) userIds.add(t.getOwnerUserId());
+ if (t.getHandlerUserId() != null) userIds.add(t.getHandlerUserId());
+ });
+ Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
+ // 3. 鎷艰鍚嶇О瀛楁
+ return BeanUtils.toBean(ticketList, AfterSaleTicketRespVO.class, vo -> {
+ CrmCustomerRespDTO customer = customerMap.get(vo.getCustomerId());
+ if (customer != null) vo.setCustomerName(customer.getName());
+ AdminUserRespDTO ownerUser = userMap.get(vo.getOwnerUserId());
+ if (ownerUser != null) vo.setOwnerUserName(ownerUser.getNickname());
+ AdminUserRespDTO handlerUser = userMap.get(vo.getHandlerUserId());
+ if (handlerUser != null) vo.setHandlerUserName(handlerUser.getNickname());
+ });
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketPageReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketPageReqVO.java
new file mode 100644
index 0000000..eef8955
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketPageReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * 鍞悗宸ュ崟鍒嗛〉鏌ヨ VO
+ */
+@Schema(description = "鍞悗宸ュ崟 - 鍒嗛〉鏌ヨ Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AfterSaleTicketPageReqVO extends PageParam {
+
+ @Schema(description = "宸ュ崟鍗曞彿", example = "AS20240101000001")
+ private String no;
+
+ @Schema(description = "宸ュ崟涓婚", example = "浜у搧澶栬缂洪櫡")
+ private String name;
+
+ @Schema(description = "瀹㈡埛缂栧彿", example = "1")
+ private Long customerId;
+
+ @Schema(description = "閿�鍞鍗曠紪鍙�", example = "1")
+ private Long saleOrderId;
+
+ @Schema(description = "宸ュ崟鐘舵��", example = "10")
+ private Integer status;
+
+ @Schema(description = "闂绫诲瀷", example = "1")
+ private Integer issueType;
+
+ @Schema(description = "鍦烘櫙绫诲瀷", example = "1")
+ private Integer sceneType;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java
new file mode 100644
index 0000000..9b25b76
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java
@@ -0,0 +1,151 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * 鍞悗宸ュ崟鍝嶅簲 VO
+ */
+@Schema(description = "鍞悗宸ュ崟 - 鍝嶅簲 VO")
+@Data
+public class AfterSaleTicketRespVO {
+
+ @Schema(description = "宸ュ崟缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "宸ュ崟鍗曞彿", example = "AS20240101000001")
+ private String no;
+
+ @Schema(description = "宸ュ崟涓婚", example = "浜у搧澶栬缂洪櫡")
+ private String name;
+
+ @Schema(description = "瀹㈡埛缂栧彿", example = "1")
+ private Long customerId;
+
+ @Schema(description = "瀹㈡埛鍚嶇О")
+ private String customerName;
+
+ @Schema(description = "鑱旂郴浜虹紪鍙�", example = "1")
+ private Long contactId;
+
+ @Schema(description = "鑱旂郴浜哄悕绉�")
+ private String contactName;
+
+ @Schema(description = "閿�鍞鍗曠紪鍙�", example = "1")
+ private Long saleOrderId;
+
+ @Schema(description = "閿�鍞鍗曞彿", example = "SO202401010001")
+ private String saleOrderNo;
+
+ @Schema(description = "鍚堝悓缂栧彿", example = "1")
+ private Long contractId;
+
+ @Schema(description = "闂绫诲瀷: 1-涓�鑸棶棰� 2-缁翠慨闂 3-閫�璐ч棶棰�", example = "1")
+ private Integer issueType;
+
+ @Schema(description = "闂鍒嗙被", example = "澶栬缂洪櫡")
+ private String issueCategory;
+
+ @Schema(description = "闂鎻忚堪", example = "鏀跺埌璐у悗鍙戠幇浜у搧琛ㄩ潰鏈夊垝鐥�")
+ private String issueDescription;
+
+ @Schema(description = "涓ラ噸绋嬪害: 0-杞诲井 1-涓�鑸� 2-涓ラ噸 3-鑷村懡", example = "1")
+ private Integer severityLevel;
+
+ @Schema(description = "鐘舵��: 0-鑽夌 10-澶勭悊涓� 30-寰呴��璐� 40-寰呴��娆�/鎹㈣揣 50-宸插畬缁� 60-宸插叧闂� 99-宸插彇娑�", example = "0")
+ private Integer status;
+
+ @Schema(description = "璐熻矗浜虹敤鎴风紪鍙�", example = "1")
+ private Long ownerUserId;
+
+ @Schema(description = "璐熻矗浜哄悕绉�")
+ private String ownerUserName;
+
+ @Schema(description = "澶勭悊浜虹敤鎴风紪鍙�", example = "2")
+ private Long handlerUserId;
+
+ @Schema(description = "澶勭悊浜哄悕绉�")
+ private String handlerUserName;
+
+ @Schema(description = "BPM 宸ヤ綔娴佸疄渚嬬紪鍙�")
+ private String processInstanceId;
+
+ @Schema(description = "鍏宠仈閫�璐х敵璇风紪鍙�", example = "1")
+ private Long returnId;
+
+ @Schema(description = "鍏宠仈缁翠慨璁板綍缂栧彿", example = "1")
+ private Long repairId;
+
+ @Schema(description = "瑙e喅鏃堕棿")
+ private LocalDateTime resolvedTime;
+
+ @Schema(description = "鍏抽棴鏃堕棿")
+ private LocalDateTime closedTime;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+
+ @Schema(description = "鍒涘缓鑰�")
+ private String creator;
+
+ @Schema(description = "鍒涘缓鏃堕棿")
+ private LocalDateTime createTime;
+
+ @Schema(description = "鏇存柊鑰�")
+ private String updater;
+
+ @Schema(description = "鏇存柊鏃堕棿")
+ private LocalDateTime updateTime;
+
+ @Schema(description = "鏄庣粏琛屽垪琛�")
+ private List<Item> items;
+
+ @Schema(description = "宸ュ崟鏄庣粏琛�")
+ @Data
+ public static class Item {
+
+ @Schema(description = "缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "宸ュ崟缂栧彿", example = "1")
+ private Long ticketId;
+
+ @Schema(description = "閿�鍞鍗曡缂栧彿", example = "1")
+ private Long saleOrderItemId;
+
+ @Schema(description = "MDM鐗╂枡缂栧彿", example = "1")
+ private Long itemId;
+
+ @Schema(description = "鐗╂枡缂栫爜", example = "MAT-001")
+ private String itemCode;
+
+ @Schema(description = "鐗╂枡鍚嶇О", example = "鐢靛瓙鍏冨櫒浠�")
+ private String itemName;
+
+ @Schema(description = "鐢熶骇鎵规缂栧彿", example = "1")
+ private Long batchId;
+
+ @Schema(description = "鐢熶骇鎵规鍙�", example = "BATCH-2024-001")
+ private String batchCode;
+
+ @Schema(description = "閿�鍞暟閲�", example = "100.000")
+ private BigDecimal saleQuantity;
+
+ @Schema(description = "閫�璐ф暟閲�", example = "10.000")
+ private BigDecimal returnQuantity;
+
+ @Schema(description = "鏄惁闇�瑕佺淮淇�", example = "false")
+ private Boolean needRepair;
+
+ @Schema(description = "鏄惁闇�瑕侀��璐�", example = "true")
+ private Boolean needReturn;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java
new file mode 100644
index 0000000..c19012e
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java
@@ -0,0 +1,89 @@
+package cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * 鍞悗宸ュ崟淇濆瓨 VO
+ */
+@Schema(description = "鍞悗宸ュ崟 - 淇濆瓨 Request VO")
+@Data
+public class AfterSaleTicketSaveReqVO {
+
+ @Schema(description = "宸ュ崟缂栧彿", example = "1")
+ private Long id;
+
+ @Schema(description = "宸ュ崟涓婚", requiredMode = Schema.RequiredMode.REQUIRED, example = "浜у搧澶栬缂洪櫡")
+ @NotEmpty(message = "宸ュ崟涓婚涓嶈兘涓虹┖")
+ private String name;
+
+ @Schema(description = "瀹㈡埛缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "瀹㈡埛涓嶈兘涓虹┖")
+ private Long customerId;
+
+ @Schema(description = "鑱旂郴浜虹紪鍙�", example = "1")
+ private Long contactId;
+
+ @Schema(description = "閿�鍞鍗曠紪鍙�", example = "1")
+ private Long saleOrderId;
+
+ @Schema(description = "閿�鍞鍗曞彿", example = "SO202401010001")
+ private String saleOrderNo;
+
+ @Schema(description = "鍚堝悓缂栧彿", example = "1")
+ private Long contractId;
+
+ @Schema(description = "闂鎻忚堪", example = "鏀跺埌璐у悗鍙戠幇浜у搧琛ㄩ潰鏈夊垝鐥�")
+ private String issueDescription;
+
+ @Schema(description = "澶囨敞", example = "瀹㈡埛瑕佹眰灏藉揩澶勭悊")
+ private String remark;
+
+ @Schema(description = "鏄庣粏琛屽垪琛�")
+ private List<Item> items;
+
+ @Schema(description = "宸ュ崟鏄庣粏琛�")
+ @Data
+ public static class Item {
+
+ @Schema(description = "閿�鍞鍗曡缂栧彿", example = "1")
+ private Long saleOrderItemId;
+
+ @Schema(description = "MDM鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "鐗╂枡涓嶈兘涓虹┖")
+ private Long itemId;
+
+ @Schema(description = "鐗╂枡缂栫爜", example = "MAT-001")
+ private String itemCode;
+
+ @Schema(description = "鐗╂枡鍚嶇О", example = "鐢靛瓙鍏冨櫒浠�")
+ private String itemName;
+
+ @Schema(description = "鐢熶骇鎵规缂栧彿", example = "1")
+ private Long batchId;
+
+ @Schema(description = "鐢熶骇鎵规鍙�", example = "BATCH-2024-001")
+ private String batchCode;
+
+ @Schema(description = "閿�鍞暟閲�", example = "100.000")
+ private BigDecimal saleQuantity;
+
+ @Schema(description = "閫�璐ф暟閲�", example = "10.000")
+ private BigDecimal returnQuantity;
+
+ @Schema(description = "鏄惁闇�瑕佺淮淇�", example = "false")
+ private Boolean needRepair;
+
+ @Schema(description = "鏄惁闇�瑕侀��璐�", example = "true")
+ private Boolean needReturn;
+
+ @Schema(description = "澶囨敞")
+ private String remark;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/permission/AfterSalePermissionDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/permission/AfterSalePermissionDO.java
new file mode 100644
index 0000000..d77e2ef
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/permission/AfterSalePermissionDO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.aftersales.dal.dataobject.permission;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 鍞悗妯″潡鏁版嵁鏉冮檺 DO
+ */
+@TableName("after_sale_permission")
+@KeySequence("after_sale_permission_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AfterSalePermissionDO extends BaseDO {
+
+ /**
+ * 缂栧彿锛屼富閿嚜澧�
+ */
+ @TableId
+ private Long id;
+
+ /**
+ * 鏁版嵁绫诲瀷
+ *
+ * 鏋氫妇 {@link AfterSaleBizTypeEnum}
+ */
+ private Integer bizType;
+ /**
+ * 鏁版嵁缂栧彿
+ *
+ * 鍏宠仈 {@link AfterSaleBizTypeEnum} 瀵瑰簲妯″潡 DO 鐨� id 瀛楁
+ */
+ private Long bizId;
+
+ /**
+ * 鐢ㄦ埛缂栧彿
+ */
+ private Long userId;
+
+ /**
+ * 鏉冮檺绾у埆
+ *
+ * 鏋氫妇 {@link AfterSalePermissionLevelEnum}
+ */
+ private Integer level;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/repair/AfterSaleRepairDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/repair/AfterSaleRepairDO.java
new file mode 100644
index 0000000..8ecb859
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/repair/AfterSaleRepairDO.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.aftersales.dal.dataobject.repair;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleRepairStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 鍞悗缁翠慨璁板綍 DO
+ */
+@TableName("after_sale_repair")
+@KeySequence("after_sale_repair_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AfterSaleRepairDO extends BaseDO {
+
+ /**
+ * 缁翠慨璁板綍缂栧彿
+ */
+ @TableId
+ private Long id;
+ /**
+ * 鍏宠仈宸ュ崟缂栧彿锛屽叧鑱� after_sale_ticket.id
+ */
+ private Long ticketId;
+ /**
+ * 缁翠慨绫诲瀷: 1-鐜板満缁翠慨 2-杩斿巶缁翠慨 3-杩滅▼鎸囧
+ */
+ private Integer repairType;
+ /**
+ * 鏁呴殰鎻忚堪
+ */
+ private String faultDescription;
+ /**
+ * 缁翠慨鎺柦
+ */
+ private String repairAction;
+ /**
+ * 缁翠慨缁撴灉: 宸蹭慨澶�/鏃犳硶淇/閮ㄥ垎淇
+ */
+ private String repairResult;
+ /**
+ * 缁翠慨璐圭敤
+ */
+ private BigDecimal repairCost;
+ /**
+ * 缁翠慨浜�
+ */
+ private Long repairUserId;
+ /**
+ * 缁翠慨鐘舵��
+ *
+ * 鏋氫妇 {@link AfterSaleRepairStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 寮�濮嬬淮淇椂闂�
+ */
+ private LocalDateTime startTime;
+ /**
+ * 瀹屾垚缁翠慨鏃堕棿
+ */
+ private LocalDateTime finishTime;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnDO.java
new file mode 100644
index 0000000..776db7f
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnDO.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj;
+
+// 娉細package 鍚嶄娇鐢� returnobj 閬垮厤涓� Java 鍏抽敭瀛� return 鍐茬獊
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleReturnStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 鍞悗閫�璐х敵璇� DO
+ */
+@TableName("after_sale_return")
+@KeySequence("after_sale_return_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AfterSaleReturnDO extends BaseDO {
+
+ /**
+ * 閫�璐х敵璇风紪鍙�
+ */
+ @TableId
+ private Long id;
+ /**
+ * 閫�璐у崟鍙�
+ */
+ private String no;
+ /**
+ * 鍏宠仈宸ュ崟缂栧彿锛屽叧鑱� after_sale_ticket.id
+ */
+ private Long ticketId;
+ /**
+ * 瀹㈡埛缂栧彿
+ */
+ private Long customerId;
+ /**
+ * 鍏宠仈閿�鍞鍗曠紪鍙�
+ */
+ private Long saleOrderId;
+ /**
+ * 閫�璐х被鍨�: 1-閫�娆鹃��璐� 2-鎹㈣揣 3-浠呴��娆�
+ */
+ private Integer returnType;
+ /**
+ * 閫�璐у師鍥�
+ */
+ private String returnReason;
+ /**
+ * 缂洪櫡鍒嗙被
+ */
+ private String defectCategory;
+ /**
+ * 搴旈��鎬婚噾棰�
+ */
+ private BigDecimal totalReturnPrice;
+ /**
+ * 瀹為��閲戦
+ */
+ private BigDecimal actualRefundPrice;
+ /**
+ * 閫�璐х姸鎬�
+ *
+ * 鏋氫妇 {@link AfterSaleReturnStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 瀹℃牳浜�
+ */
+ private Long auditUserId;
+ /**
+ * 瀹℃牳鏃堕棿
+ */
+ private LocalDateTime auditTime;
+ /**
+ * MES 閫�璐у崟缂栧彿锛屽叧鑱� mes_wm_return_sales.id
+ */
+ private Long mesReturnId;
+ /**
+ * MES 閫�璐у崟鍙�(鍐椾綑)
+ */
+ private String mesReturnCode;
+ /**
+ * 璐ㄦ缁撹: 鍚堟牸/鐗归噰/閫�璐�/鎶ュ簾
+ */
+ private String qualityResult;
+ /**
+ * 璐ㄦ鎶ュ憡闄勪欢URL
+ */
+ private String qualityReportUrl;
+ /**
+ * 閫�璐у叆搴撳畬鎴愭椂闂�
+ */
+ private LocalDateTime returnInboundTime;
+ /**
+ * 閫�娆�/鎹㈣揣瀹屾垚鏃堕棿
+ */
+ private LocalDateTime refundTime;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnItemDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnItemDO.java
new file mode 100644
index 0000000..53bc524
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/returnobj/AfterSaleReturnItemDO.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * 鍞悗閫�璐ф槑缁嗚 DO
+ */
+@TableName("after_sale_return_item")
+@KeySequence("after_sale_return_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AfterSaleReturnItemDO extends BaseDO {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId
+ private Long id;
+ /**
+ * 閫�璐х敵璇风紪鍙凤紝鍏宠仈 after_sale_return.id
+ */
+ private Long returnId;
+ /**
+ * 宸ュ崟鏄庣粏琛岀紪鍙�
+ */
+ private Long ticketItemId;
+ /**
+ * MDM 鐗╂枡缂栧彿
+ */
+ private Long itemId;
+ /**
+ * 鐗╂枡缂栫爜(鍐椾綑)
+ */
+ private String itemCode;
+ /**
+ * 鐗╂枡鍚嶇О(鍐椾綑)
+ */
+ private String itemName;
+ /**
+ * 鎵规缂栧彿
+ */
+ private Long batchId;
+ /**
+ * 鎵规鍙�
+ */
+ private String batchCode;
+ /**
+ * 閫�璐ф暟閲�
+ */
+ private BigDecimal returnQuantity;
+ /**
+ * 閿�鍞崟浠�
+ */
+ private BigDecimal unitPrice;
+ /**
+ * 閫�璐ч噾棰�
+ */
+ private BigDecimal returnPrice;
+ /**
+ * MES 閫�璐у崟琛岀紪鍙�
+ */
+ private Long mesReturnLineId;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketDO.java
new file mode 100644
index 0000000..a2eee67
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketDO.java
@@ -0,0 +1,116 @@
+package cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleIssueTypeEnum;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDateTime;
+
+/**
+ * 鍞悗宸ュ崟 DO
+ */
+@TableName("after_sale_ticket")
+@KeySequence("after_sale_ticket_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AfterSaleTicketDO extends BaseDO {
+
+ /**
+ * 宸ュ崟缂栧彿
+ */
+ @TableId
+ private Long id;
+ /**
+ * 宸ュ崟鍗曞彿
+ */
+ private String no;
+ /**
+ * 宸ュ崟涓婚/鏍囬
+ */
+ private String name;
+ /**
+ * 瀹㈡埛缂栧彿锛屽叧鑱� crm_customer.id
+ */
+ private Long customerId;
+ /**
+ * 鑱旂郴浜虹紪鍙凤紝鍏宠仈 crm_contact.id
+ */
+ private Long contactId;
+ /**
+ * 閿�鍞鍗曠紪鍙凤紝鍏宠仈 erp_sale_order.id
+ */
+ private Long saleOrderId;
+ /**
+ * 閿�鍞鍗曞彿(鍐椾綑)
+ */
+ private String saleOrderNo;
+ /**
+ * 鍚堝悓缂栧彿锛屽叧鑱� crm_contract.id
+ */
+ private Long contractId;
+ /**
+ * 闂绫诲瀷
+ *
+ * 鏋氫妇 {@link AfterSaleIssueTypeEnum}
+ */
+ private Integer issueType;
+ /**
+ * 闂鍒嗙被: 澶栬缂洪櫡/鍔熻兘鏁呴殰/灏哄鍋忓樊/鍖呰鐮存崯/鍏朵粬
+ */
+ private String issueCategory;
+ /**
+ * 闂鎻忚堪
+ */
+ private String issueDescription;
+ /**
+ * 涓ラ噸绋嬪害: 0-杞诲井 1-涓�鑸� 2-涓ラ噸 3-鑷村懡
+ */
+ private Integer severityLevel;
+ /**
+ * 宸ュ崟鐘舵��
+ *
+ * 鏋氫妇 {@link AfterSaleTicketStatusEnum}
+ */
+ private Integer status;
+ /**
+ * 璐熻矗浜虹敤鎴风紪鍙�
+ */
+ private Long ownerUserId;
+ /**
+ * 澶勭悊浜虹敤鎴风紪鍙�
+ */
+ private Long handlerUserId;
+ /**
+ * BPM 宸ヤ綔娴佸疄渚嬬紪鍙�
+ */
+ private String processInstanceId;
+ /**
+ * 鍏宠仈閫�璐х敵璇风紪鍙凤紝鍏宠仈 after_sale_return.id
+ */
+ private Long returnId;
+ /**
+ * 鍏宠仈缁翠慨璁板綍缂栧彿锛屽叧鑱� after_sale_repair.id
+ */
+ private Long repairId;
+ /**
+ * 瑙e喅鏃堕棿
+ */
+ private LocalDateTime resolvedTime;
+ /**
+ * 鍏抽棴鏃堕棿
+ */
+ private LocalDateTime closedTime;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java
new file mode 100644
index 0000000..28af429
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+
+/**
+ * 鍞悗宸ュ崟鏄庣粏琛� DO
+ */
+@TableName("after_sale_ticket_item")
+@KeySequence("after_sale_ticket_item_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class AfterSaleTicketItemDO extends BaseDO {
+
+ /**
+ * 缂栧彿
+ */
+ @TableId
+ private Long id;
+ /**
+ * 宸ュ崟缂栧彿锛屽叧鑱� after_sale_ticket.id
+ */
+ private Long ticketId;
+ /**
+ * 閿�鍞鍗曡缂栧彿
+ */
+ private Long saleOrderItemId;
+ /**
+ * MDM 鐗╂枡缂栧彿
+ */
+ private Long itemId;
+ /**
+ * 鐗╂枡缂栫爜(鍐椾綑)
+ */
+ private String itemCode;
+ /**
+ * 鐗╂枡鍚嶇О(鍐椾綑)
+ */
+ private String itemName;
+ /**
+ * 鐢熶骇鎵规缂栧彿
+ */
+ private Long batchId;
+ /**
+ * 鐢熶骇鎵规鍙�
+ */
+ private String batchCode;
+ /**
+ * 閿�鍞暟閲�
+ */
+ private BigDecimal saleQuantity;
+ /**
+ * 閫�璐ф暟閲�
+ */
+ private BigDecimal returnQuantity;
+ /**
+ * 鏄惁闇�瑕佺淮淇�
+ */
+ private Boolean needRepair;
+ /**
+ * 鏄惁闇�瑕侀��璐�
+ */
+ private Boolean needReturn;
+ /**
+ * 澶囨敞
+ */
+ private String remark;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/permission/AfterSalePermissionMapper.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/permission/AfterSalePermissionMapper.java
new file mode 100644
index 0000000..8daa058
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/permission/AfterSalePermissionMapper.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.aftersales.dal.mysql.permission;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.permission.AfterSalePermissionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍞悗妯″潡鏁版嵁鏉冮檺 Mapper
+ */
+@Mapper
+public interface AfterSalePermissionMapper extends BaseMapperX<AfterSalePermissionDO> {
+
+ default List<AfterSalePermissionDO> selectListByBiz(Integer bizType, Long bizId) {
+ return selectList(new LambdaQueryWrapperX<AfterSalePermissionDO>()
+ .eq(AfterSalePermissionDO::getBizType, bizType)
+ .eq(AfterSalePermissionDO::getBizId, bizId));
+ }
+
+ default List<AfterSalePermissionDO> selectListByBiz(Integer bizType, Collection<Long> bizIds) {
+ return selectList(new LambdaQueryWrapperX<AfterSalePermissionDO>()
+ .eq(AfterSalePermissionDO::getBizType, bizType)
+ .in(AfterSalePermissionDO::getBizId, bizIds));
+ }
+
+ default List<AfterSalePermissionDO> selectListByBizTypeAndUserId(Integer bizType, Long userId) {
+ return selectList(new LambdaQueryWrapperX<AfterSalePermissionDO>()
+ .eq(AfterSalePermissionDO::getBizType, bizType)
+ .eq(AfterSalePermissionDO::getUserId, userId));
+ }
+
+ default AfterSalePermissionDO selectByBizAndUser(Integer bizType, Long bizId, Long userId) {
+ return selectOne(new LambdaQueryWrapperX<AfterSalePermissionDO>()
+ .eq(AfterSalePermissionDO::getBizType, bizType)
+ .eq(AfterSalePermissionDO::getBizId, bizId)
+ .eq(AfterSalePermissionDO::getUserId, userId));
+ }
+
+ default void deleteByBiz(Integer bizType, Long bizId) {
+ delete(new LambdaQueryWrapperX<AfterSalePermissionDO>()
+ .eq(AfterSalePermissionDO::getBizType, bizType)
+ .eq(AfterSalePermissionDO::getBizId, bizId));
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/repair/AfterSaleRepairMapper.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/repair/AfterSaleRepairMapper.java
new file mode 100644
index 0000000..3ac344c
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/repair/AfterSaleRepairMapper.java
@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.aftersales.dal.mysql.repair;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.repair.AfterSaleRepairDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍞悗缁翠慨璁板綍 Mapper
+ */
+@Mapper
+public interface AfterSaleRepairMapper extends BaseMapperX<AfterSaleRepairDO> {
+
+ default AfterSaleRepairDO selectByTicketId(Long ticketId) {
+ return selectOne(AfterSaleRepairDO::getTicketId, ticketId);
+ }
+
+ default List<AfterSaleRepairDO> selectListByTicketId(Long ticketId) {
+ return selectList(new LambdaQueryWrapperX<AfterSaleRepairDO>()
+ .eq(AfterSaleRepairDO::getTicketId, ticketId));
+ }
+
+ default PageResult<AfterSaleRepairDO> selectPage(PageParam pageParam, Long ticketId, Integer status) {
+ return selectPage(pageParam, new LambdaQueryWrapperX<AfterSaleRepairDO>()
+ .eqIfPresent(AfterSaleRepairDO::getTicketId, ticketId)
+ .eqIfPresent(AfterSaleRepairDO::getStatus, status)
+ .orderByDesc(AfterSaleRepairDO::getId));
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnItemMapper.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnItemMapper.java
new file mode 100644
index 0000000..d9d9ece
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnItemMapper.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍞悗閫�璐ф槑缁嗚 Mapper
+ */
+@Mapper
+public interface AfterSaleReturnItemMapper extends BaseMapperX<AfterSaleReturnItemDO> {
+
+ default List<AfterSaleReturnItemDO> selectListByReturnId(Long returnId) {
+ return selectList(new LambdaQueryWrapperX<AfterSaleReturnItemDO>()
+ .eq(AfterSaleReturnItemDO::getReturnId, returnId));
+ }
+
+ default void deleteByReturnId(Long returnId) {
+ delete(new LambdaQueryWrapperX<AfterSaleReturnItemDO>()
+ .eq(AfterSaleReturnItemDO::getReturnId, returnId));
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnMapper.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnMapper.java
new file mode 100644
index 0000000..eb0ac14
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/returnobj/AfterSaleReturnMapper.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.util.AfterSalePermissionUtils;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 鍞悗閫�璐х敵璇� Mapper
+ */
+@Mapper
+public interface AfterSaleReturnMapper extends BaseMapperX<AfterSaleReturnDO> {
+
+ default AfterSaleReturnDO selectByNo(String no) {
+ return selectOne(AfterSaleReturnDO::getNo, no);
+ }
+
+ default AfterSaleReturnDO selectByTicketId(Long ticketId) {
+ return selectOne(AfterSaleReturnDO::getTicketId, ticketId);
+ }
+
+ default PageResult<AfterSaleReturnDO> selectPage(PageParam pageParam, Long userId, Integer sceneType,
+ String no, Long ticketId,
+ Long customerId, Integer status) {
+ MPJLambdaWrapperX<AfterSaleReturnDO> query = new MPJLambdaWrapperX<>();
+ // 鏁版嵁鏉冮檺
+ AfterSalePermissionUtils.appendPermissionCondition(query, AfterSaleBizTypeEnum.AFTER_SALE_RETURN.getType(),
+ AfterSaleReturnDO::getId, userId, sceneType);
+ // 鏌ヨ鏉′欢
+ query.selectAll(AfterSaleReturnDO.class)
+ .likeIfPresent(AfterSaleReturnDO::getNo, no)
+ .eqIfPresent(AfterSaleReturnDO::getTicketId, ticketId)
+ .eqIfPresent(AfterSaleReturnDO::getCustomerId, customerId)
+ .eqIfPresent(AfterSaleReturnDO::getStatus, status)
+ .orderByDesc(AfterSaleReturnDO::getId);
+ return selectJoinPage(pageParam, AfterSaleReturnDO.class, query);
+ }
+
+ default AfterSaleReturnDO selectByMesReturnId(Long mesReturnId) {
+ return selectOne(AfterSaleReturnDO::getMesReturnId, mesReturnId);
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketItemMapper.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketItemMapper.java
new file mode 100644
index 0000000..ea609b9
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketItemMapper.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.aftersales.dal.mysql.ticket;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍞悗宸ュ崟鏄庣粏琛� Mapper
+ */
+@Mapper
+public interface AfterSaleTicketItemMapper extends BaseMapperX<AfterSaleTicketItemDO> {
+
+ default List<AfterSaleTicketItemDO> selectListByTicketId(Long ticketId) {
+ return selectList(new LambdaQueryWrapperX<AfterSaleTicketItemDO>()
+ .eq(AfterSaleTicketItemDO::getTicketId, ticketId));
+ }
+
+ default void deleteByTicketId(Long ticketId) {
+ delete(new LambdaQueryWrapperX<AfterSaleTicketItemDO>()
+ .eq(AfterSaleTicketItemDO::getTicketId, ticketId));
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketMapper.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketMapper.java
new file mode 100644
index 0000000..2546020
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/mysql/ticket/AfterSaleTicketMapper.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.aftersales.dal.mysql.ticket;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.util.AfterSalePermissionUtils;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 鍞悗宸ュ崟 Mapper
+ */
+@Mapper
+public interface AfterSaleTicketMapper extends BaseMapperX<AfterSaleTicketDO> {
+
+ default AfterSaleTicketDO selectByNo(String no) {
+ return selectOne(AfterSaleTicketDO::getNo, no);
+ }
+
+ default PageResult<AfterSaleTicketDO> selectPage(PageParam pageParam, Long userId, Integer sceneType,
+ String no, String name,
+ Long customerId, Long saleOrderId,
+ Integer status, Integer issueType) {
+ MPJLambdaWrapperX<AfterSaleTicketDO> query = new MPJLambdaWrapperX<>();
+ // 鏁版嵁鏉冮檺
+ AfterSalePermissionUtils.appendPermissionCondition(query, AfterSaleBizTypeEnum.AFTER_SALE_TICKET.getType(),
+ AfterSaleTicketDO::getId, userId, sceneType);
+ // 鏌ヨ鏉′欢
+ query.selectAll(AfterSaleTicketDO.class)
+ .likeIfPresent(AfterSaleTicketDO::getNo, no)
+ .likeIfPresent(AfterSaleTicketDO::getName, name)
+ .eqIfPresent(AfterSaleTicketDO::getCustomerId, customerId)
+ .eqIfPresent(AfterSaleTicketDO::getSaleOrderId, saleOrderId)
+ .eqIfPresent(AfterSaleTicketDO::getStatus, status)
+ .eqIfPresent(AfterSaleTicketDO::getIssueType, issueType)
+ .orderByDesc(AfterSaleTicketDO::getId);
+ return selectJoinPage(pageParam, AfterSaleTicketDO.class, query);
+ }
+
+ default List<AfterSaleTicketDO> selectListByCustomerId(Long customerId) {
+ return selectList(new LambdaQueryWrapperX<AfterSaleTicketDO>()
+ .eq(AfterSaleTicketDO::getCustomerId, customerId));
+ }
+
+ default AfterSaleTicketDO selectBySaleOrderId(Long saleOrderId) {
+ return selectOne(AfterSaleTicketDO::getSaleOrderId, saleOrderId);
+ }
+
+ default Long selectCountByOwnerUserId(Long ownerUserId) {
+ return selectCount(AfterSaleTicketDO::getOwnerUserId, ownerUserId);
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/RedisKeyConstants.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/RedisKeyConstants.java
new file mode 100644
index 0000000..575dde6
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/RedisKeyConstants.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.aftersales.dal.redis;
+
+/**
+ * 鍞悗妯″潡 Redis Key 甯搁噺
+ */
+public interface RedisKeyConstants {
+
+ /**
+ * 搴忓彿鐨勭紦瀛�
+ *
+ * KEY 鏍煎紡锛歛ftersales:seq_no:{prefix}
+ * VALUE 鏁版嵁鏍煎紡锛氱紪鍙疯嚜澧�
+ */
+ String NO = "aftersales:seq_no:";
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/no/AfterSaleNoRedisDAO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/no/AfterSaleNoRedisDAO.java
new file mode 100644
index 0000000..856c862
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/redis/no/AfterSaleNoRedisDAO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.aftersales.dal.redis.no;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import cn.iocoder.yudao.module.aftersales.dal.redis.RedisKeyConstants;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+/**
+ * 鍞悗妯″潡鍗曞彿 Redis DAO
+ */
+@Repository
+public class AfterSaleNoRedisDAO {
+
+ /**
+ * 鍞悗宸ュ崟 {@link cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO}
+ */
+ public static final String TICKET_NO_PREFIX = "AS";
+
+ /**
+ * 閫�璐х敵璇� {@link cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO}
+ */
+ public static final String RETURN_NO_PREFIX = "RT";
+
+ @Resource
+ private StringRedisTemplate stringRedisTemplate;
+
+ /**
+ * 鐢熸垚搴忓彿锛屼娇鐢ㄥ綋鍓嶆棩鏈燂紝鏍煎紡涓� {PREFIX} + yyyyMMdd + 6 浣嶈嚜澧�
+ *
+ * @param prefix 鍓嶇紑
+ * @return 搴忓彿
+ */
+ public String generate(String prefix) {
+ String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN);
+ String key = RedisKeyConstants.NO + noPrefix;
+ Long no = stringRedisTemplate.opsForValue().increment(key);
+ stringRedisTemplate.expire(key, Duration.ofDays(1L));
+ return noPrefix + String.format("%06d", no);
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleBizTypeEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleBizTypeEnum.java
new file mode 100644
index 0000000..5408b5a
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleBizTypeEnum.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗涓氬姟绫诲瀷鏋氫妇
+ */
+@RequiredArgsConstructor
+@Getter
+public enum AfterSaleBizTypeEnum implements ArrayValuable<Integer> {
+
+ AFTER_SALE_TICKET(1, "鍞悗宸ュ崟"),
+ AFTER_SALE_RETURN(2, "閫�璐х敵璇�"),
+ AFTER_SALE_REPAIR(3, "缁翠慨璁板綍"),
+ ;
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(AfterSaleBizTypeEnum::getType).toArray(Integer[]::new);
+
+ private final Integer type;
+ private final String name;
+
+ public static String getNameByType(Integer type) {
+ AfterSaleBizTypeEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(AfterSaleBizTypeEnum.values()),
+ item -> ObjUtil.equal(item.type, type));
+ return typeEnum == null ? null : typeEnum.getName();
+ }
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java
new file mode 100644
index 0000000..ff45d51
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleIssueTypeEnum.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗闂绫诲瀷鏋氫妇
+ */
+@RequiredArgsConstructor
+@Getter
+public enum AfterSaleIssueTypeEnum implements ArrayValuable<Integer> {
+
+ UNCLASSIFIED(0, "鏈垽瀹�"),
+ GENERAL(1, "涓�鑸棶棰�"),
+ REPAIR(2, "缁翠慨闂"),
+ RETURN(3, "閫�璐ч棶棰�");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(AfterSaleIssueTypeEnum::getType).toArray(Integer[]::new);
+
+ private final Integer type;
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleRepairStatusEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleRepairStatusEnum.java
new file mode 100644
index 0000000..33a8cc8
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleRepairStatusEnum.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗缁翠慨鐘舵�佹灇涓�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum AfterSaleRepairStatusEnum implements ArrayValuable<Integer> {
+
+ PENDING(0, "寰呭鐞�"),
+ IN_PROGRESS(10, "缁翠慨涓�"),
+ COMPLETED(20, "宸插畬鎴�"),
+ CLOSED(30, "宸插叧闂�");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(AfterSaleRepairStatusEnum::getStatus).toArray(Integer[]::new);
+
+ private final Integer status;
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleReturnStatusEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleReturnStatusEnum.java
new file mode 100644
index 0000000..634b540
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleReturnStatusEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗閫�璐х姸鎬佹灇涓�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum AfterSaleReturnStatusEnum implements ArrayValuable<Integer> {
+
+ DRAFT(0, "鑽夌"),
+ AUDITING(10, "閿�鍞鏍镐腑"),
+ APPROVED(20, "瀹℃牳閫氳繃-宸插悓姝ES"),
+ INSPECTING(30, "妫�楠屼腑"),
+ INSPECTED(40, "妫�楠屽畬鎴�"),
+ INBOUND(50, "宸插叆搴�"),
+ REFUNDED(60, "閫�娆�/鎹㈣揣瀹屾垚"),
+ CLOSED(70, "宸插叧闂�"),
+ CANCELED(99, "宸插彇娑�");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(AfterSaleReturnStatusEnum::getStatus).toArray(Integer[]::new);
+
+ private final Integer status;
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleTicketStatusEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleTicketStatusEnum.java
new file mode 100644
index 0000000..88f6da8
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/AfterSaleTicketStatusEnum.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗宸ュ崟鐘舵�佹灇涓�
+ */
+@RequiredArgsConstructor
+@Getter
+public enum AfterSaleTicketStatusEnum implements ArrayValuable<Integer> {
+
+ DRAFT(0, "鑽夌"),
+ PROCESSING(10, "澶勭悊涓�"),
+ WAITING_RETURN(30, "寰呴��璐�"),
+ WAITING_REFUND(40, "寰呴��娆�/鎹㈣揣"),
+ COMPLETED(50, "宸插畬缁�"),
+ CLOSED(60, "宸插叧闂�"),
+ CANCELED(99, "宸插彇娑�");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values())
+ .map(AfterSaleTicketStatusEnum::getStatus).toArray(Integer[]::new);
+
+ private final Integer status;
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java
new file mode 100644
index 0000000..1796711
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/ErrorCodeConstants.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+
+/**
+ * 鍞悗妯″潡閿欒鐮佹灇涓剧被
+ * <p>
+ * aftersales 绯荤粺锛屼娇鐢� 1-022-000-000 娈�
+ */
+public interface ErrorCodeConstants {
+
+ // ========== 鍞悗宸ュ崟 1-022-000-000 ==========
+ ErrorCode TICKET_NOT_EXISTS = new ErrorCode(1_022_000_000, "鍞悗宸ュ崟涓嶅瓨鍦�");
+ ErrorCode TICKET_NO_EXISTS = new ErrorCode(1_022_000_001, "鐢熸垚宸ュ崟鍙烽噸澶嶏紝璇烽噸璇�");
+ ErrorCode TICKET_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_022_000_002, "宸ュ崟涓嶆槸鑽夌鐘舵�侊紝鏃犳硶鍒犻櫎");
+ ErrorCode TICKET_UPDATE_FAIL_STATUS = new ErrorCode(1_022_000_003, "鏇存柊宸ュ崟鐘舵�佸け璐ワ紝鍘熷洜锛氬凡缁忔槸璇ョ姸鎬�");
+ ErrorCode TICKET_RESOLVE_FAIL = new ErrorCode(1_022_000_004, "瀹岀粨宸ュ崟澶辫触锛屽師鍥狅細宸ュ崟涓嶆槸澶勭悊涓姸鎬�");
+ ErrorCode TICKET_CLOSE_FAIL = new ErrorCode(1_022_000_005, "鍏抽棴宸ュ崟澶辫触锛屽師鍥狅細宸ュ崟鏈畬缁�");
+ ErrorCode TICKET_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_022_000_006, "鎻愪氦宸ュ崟瀹℃壒澶辫触锛屽師鍥狅細宸ュ崟涓嶆槸鑽夌鐘舵��");
+ ErrorCode TICKET_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_022_000_007, "鍞悗宸ュ崟瀹℃壒娴佺▼涓嶅瓨鍦ㄦ垨鏈縺娲伙紝璇锋鏌� BPM 閰嶇疆");
+ ErrorCode TICKET_STATUS_ERROR = new ErrorCode(1_022_000_008, "宸ュ崟褰撳墠鐘舵�佷笉鍏佽姝ゆ搷浣�");
+
+ // ========== 鍞悗宸ュ崟鏄庣粏琛� 1-022-001-000 ==========
+ ErrorCode TICKET_ITEM_NOT_EXISTS = new ErrorCode(1_022_001_000, "宸ュ崟鏄庣粏琛屼笉瀛樺湪");
+ ErrorCode TICKET_ITEM_EMPTY = new ErrorCode(1_022_001_001, "宸ュ崟鏄庣粏琛屼笉鑳戒负绌�");
+
+ // ========== 閫�璐х敵璇� 1-022-002-000 ==========
+ ErrorCode RETURN_NOT_EXISTS = new ErrorCode(1_022_002_000, "閫�璐х敵璇蜂笉瀛樺湪");
+ ErrorCode RETURN_NO_EXISTS = new ErrorCode(1_022_002_001, "鐢熸垚閫�璐у崟鍙烽噸澶嶏紝璇烽噸璇�");
+ ErrorCode RETURN_APPROVE_FAIL = new ErrorCode(1_022_002_002, "瀹℃牳閫�璐х敵璇峰け璐ワ紝鍘熷洜锛氫笉鏄緟瀹℃牳鐘舵��");
+ ErrorCode RETURN_ITEM_EMPTY = new ErrorCode(1_022_002_003, "閫�璐ф槑缁嗚涓嶈兘涓虹┖");
+ ErrorCode RETURN_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_022_002_004, "閫�璐х敵璇蜂笉鏄崏绋跨姸鎬侊紝鏃犳硶鍒犻櫎");
+
+ // ========== 缁翠慨璁板綍 1-022-003-000 ==========
+ ErrorCode REPAIR_NOT_EXISTS = new ErrorCode(1_022_003_000, "缁翠慨璁板綍涓嶅瓨鍦�");
+ ErrorCode REPAIR_UPDATE_FAIL_STATUS = new ErrorCode(1_022_003_001, "鏇存柊缁翠慨鐘舵�佸け璐ワ紝鍘熷洜锛氬凡缁忔槸璇ョ姸鎬�");
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/LogRecordConstants.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/LogRecordConstants.java
new file mode 100644
index 0000000..0fd4527
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/LogRecordConstants.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.aftersales.enums;
+
+/**
+ * 鍞悗妯″潡鎿嶄綔鏃ュ織甯搁噺
+ */
+public interface LogRecordConstants {
+
+ // ======================= 鍞悗宸ュ崟 =======================
+
+ String AFTER_SALE_TICKET_TYPE = "鍞悗宸ュ崟";
+ String AFTER_SALE_TICKET_CREATE_SUB_TYPE = "鍒涘缓鍞悗宸ュ崟";
+ String AFTER_SALE_TICKET_CREATE_SUCCESS = "鍒涘缓浜嗗敭鍚庡伐鍗曘�恵{#ticket.name}}銆�";
+ String AFTER_SALE_TICKET_UPDATE_SUB_TYPE = "鏇存柊鍞悗宸ュ崟";
+ String AFTER_SALE_TICKET_UPDATE_SUCCESS = "鏇存柊浜嗗敭鍚庡伐鍗曘�恵{#ticketName}}銆�: {_DIFF{#updateReqVO}}";
+ String AFTER_SALE_TICKET_DELETE_SUB_TYPE = "鍒犻櫎鍞悗宸ュ崟";
+ String AFTER_SALE_TICKET_DELETE_SUCCESS = "鍒犻櫎浜嗗敭鍚庡伐鍗曘�恵{#ticketName}}銆�";
+ String AFTER_SALE_TICKET_ASSIGN_SUB_TYPE = "鍒嗛厤澶勭悊浜�";
+ String AFTER_SALE_TICKET_ASSIGN_SUCCESS = "灏嗗敭鍚庡伐鍗曘�恵{#ticketName}}銆戝垎閰嶇粰銆恵getAdminUserById{#handlerUserId}}銆�";
+ String AFTER_SALE_TICKET_CLASSIFY_SUB_TYPE = "闂鍒ゅ畾";
+ String AFTER_SALE_TICKET_CLASSIFY_SUCCESS = "瀵瑰敭鍚庡伐鍗曘�恵{#ticketName}}銆戣繘琛屼簡闂鍒ゅ畾锛氥�恵{#issueTypeName}}銆�";
+ String AFTER_SALE_TICKET_RESOLVE_SUB_TYPE = "瀹岀粨宸ュ崟";
+ String AFTER_SALE_TICKET_RESOLVE_SUCCESS = "瀹岀粨浜嗗敭鍚庡伐鍗曘�恵{#ticketName}}銆�";
+ String AFTER_SALE_TICKET_CLOSE_SUB_TYPE = "鍏抽棴宸ュ崟";
+ String AFTER_SALE_TICKET_CLOSE_SUCCESS = "鍏抽棴浜嗗敭鍚庡伐鍗曘�恵{#ticketName}}銆�";
+ String AFTER_SALE_TICKET_CANCEL_SUB_TYPE = "鍙栨秷宸ュ崟";
+ String AFTER_SALE_TICKET_CANCEL_SUCCESS = "鍙栨秷浜嗗敭鍚庡伐鍗曘�恵{#ticketName}}銆�";
+ String AFTER_SALE_TICKET_SUBMIT_SUB_TYPE = "鎻愪氦宸ュ崟瀹℃壒";
+ String AFTER_SALE_TICKET_SUBMIT_SUCCESS = "鎻愪氦鍞悗宸ュ崟銆恵{#ticketName}}銆戝鎵规垚鍔�";
+
+ // ======================= 閫�璐х敵璇� =======================
+
+ String AFTER_SALE_RETURN_TYPE = "鍞悗閫�璐�";
+ String AFTER_SALE_RETURN_CREATE_SUB_TYPE = "鍒涘缓閫�璐х敵璇�";
+ String AFTER_SALE_RETURN_CREATE_SUCCESS = "鍒涘缓浜嗛��璐х敵璇枫�恵{#returnDO.no}}銆�";
+ String AFTER_SALE_RETURN_APPROVE_SUB_TYPE = "瀹℃牳閫�璐х敵璇�";
+ String AFTER_SALE_RETURN_APPROVE_SUCCESS = "瀹℃牳閫氳繃浜嗛��璐х敵璇枫�恵{#returnNo}}銆戯紝宸插悓姝ュ埌MES閫�璐�";
+ String AFTER_SALE_RETURN_DELETE_SUB_TYPE = "鍒犻櫎閫�璐х敵璇�";
+ String AFTER_SALE_RETURN_DELETE_SUCCESS = "鍒犻櫎浜嗛��璐х敵璇枫�恵{#returnNo}}銆�";
+
+ // ======================= 缁翠慨璁板綍 =======================
+
+ String AFTER_SALE_REPAIR_TYPE = "鍞悗缁翠慨";
+ String AFTER_SALE_REPAIR_CREATE_SUB_TYPE = "鍒涘缓缁翠慨璁板綍";
+ String AFTER_SALE_REPAIR_CREATE_SUCCESS = "鍒涘缓浜嗙淮淇褰曘�恵{#repair.id}}銆�";
+ String AFTER_SALE_REPAIR_COMPLETE_SUB_TYPE = "瀹屾垚缁翠慨";
+ String AFTER_SALE_REPAIR_COMPLETE_SUCCESS = "瀹屾垚浜嗙淮淇褰曘�恵{#repairId}}銆戯紝缁翠慨缁撴灉锛氥�恵{#repairResult}}銆�";
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSalePermissionLevelEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSalePermissionLevelEnum.java
new file mode 100644
index 0000000..d086d2d
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSalePermissionLevelEnum.java
@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.aftersales.enums.permission;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗妯″潡鏁版嵁鏉冮檺绾у埆鏋氫妇
+ *
+ * OWNER > WRITE > READ
+ */
+@Getter
+@AllArgsConstructor
+public enum AfterSalePermissionLevelEnum implements ArrayValuable<Integer> {
+
+ OWNER(1, "璐熻矗浜�"),
+ READ(2, "鍙"),
+ WRITE(3, "璇诲啓");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSalePermissionLevelEnum::getLevel).toArray(Integer[]::new);
+
+ /**
+ * 绾у埆
+ */
+ private final Integer level;
+ /**
+ * 绾у埆鍚嶇О
+ */
+ private final String name;
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+ public static boolean isOwner(Integer level) {
+ return ObjUtil.equal(OWNER.level, level);
+ }
+
+ public static boolean isRead(Integer level) {
+ return ObjUtil.equal(READ.level, level);
+ }
+
+ public static boolean isWrite(Integer level) {
+ return ObjUtil.equal(WRITE.level, level);
+ }
+
+ public static String getNameByLevel(Integer level) {
+ AfterSalePermissionLevelEnum typeEnum = CollUtil.findOne(CollUtil.newArrayList(AfterSalePermissionLevelEnum.values()),
+ item -> ObjUtil.equal(item.level, level));
+ return typeEnum == null ? null : typeEnum.getName();
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSaleSceneTypeEnum.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSaleSceneTypeEnum.java
new file mode 100644
index 0000000..ef371dd
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/enums/permission/AfterSaleSceneTypeEnum.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.aftersales.enums.permission;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 鍞悗妯″潡鍒楄〃妫�绱㈠満鏅�
+ */
+@Getter
+@AllArgsConstructor
+public enum AfterSaleSceneTypeEnum implements ArrayValuable<Integer> {
+
+ OWNER(1, "鎴戣礋璐g殑"),
+ INVOLVED(2, "鎴戝弬涓庣殑"),
+ SUBORDINATE(3, "涓嬪睘璐熻矗鐨�"),
+ ALL(4, "鍏ㄩ儴");
+
+ public static final Integer[] ARRAYS = Arrays.stream(values()).map(AfterSaleSceneTypeEnum::getType).toArray(Integer[]::new);
+
+ /**
+ * 鍦烘櫙绫诲瀷
+ */
+ private final Integer type;
+ /**
+ * 鍦烘櫙鍚嶇О
+ */
+ private final String name;
+
+ public static boolean isOwner(Integer type) {
+ return ObjUtil.equal(OWNER.getType(), type);
+ }
+
+ public static boolean isInvolved(Integer type) {
+ return ObjUtil.equal(INVOLVED.getType(), type);
+ }
+
+ public static boolean isSubordinate(Integer type) {
+ return ObjUtil.equal(SUBORDINATE.getType(), type);
+ }
+
+ public static boolean isAll(Integer type) {
+ return ObjUtil.equal(ALL.getType(), type);
+ }
+
+ @Override
+ public Integer[] array() {
+ return ARRAYS;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionService.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionService.java
new file mode 100644
index 0000000..3426041
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionService.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.aftersales.service.permission;
+
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.permission.AfterSalePermissionDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum;
+import cn.iocoder.yudao.module.aftersales.service.permission.bo.AfterSalePermissionCreateReqBO;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍞悗妯″潡鏁版嵁鏉冮檺 Service 鎺ュ彛
+ */
+public interface AfterSalePermissionService {
+
+ /**
+ * 鍒涘缓鏁版嵁鏉冮檺锛堣礋璐d汉鏉冮檺锛�
+ *
+ * @param createReqBO 鍒涘缓淇℃伅
+ * @return 缂栧彿
+ */
+ Long createPermission(AfterSalePermissionCreateReqBO createReqBO);
+
+ /**
+ * 鍒犻櫎鏁版嵁鏉冮檺
+ *
+ * @param bizType 鏁版嵁绫诲瀷锛屽叧鑱� {@link AfterSaleBizTypeEnum}
+ * @param bizId 鏁版嵁缂栧彿
+ */
+ void deletePermission(Integer bizType, Long bizId);
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺鍒楄〃
+ *
+ * @param bizType 鏁版嵁绫诲瀷
+ * @param bizId 鏁版嵁缂栧彿
+ * @return 鏉冮檺鍒楄〃
+ */
+ List<AfterSalePermissionDO> getPermissionListByBiz(Integer bizType, Long bizId);
+
+ /**
+ * 鑾峰彇鏁版嵁鏉冮檺鍒楄〃锛堟壒閲忥級
+ *
+ * @param bizType 鏁版嵁绫诲瀷
+ * @param bizIds 鏁版嵁缂栧彿鍒楄〃
+ * @return 鏉冮檺鍒楄〃
+ */
+ List<AfterSalePermissionDO> getPermissionListByBiz(Integer bizType, Collection<Long> bizIds);
+
+ /**
+ * 鏍¢獙鏄惁鏈夋寚瀹氭暟鎹殑鎿嶄綔鏉冮檺
+ *
+ * @param bizType 鏁版嵁绫诲瀷
+ * @param bizId 鏁版嵁缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @param level 鏉冮檺绾у埆
+ * @return 鏄惁鏈夋潈闄�
+ */
+ boolean hasPermission(Integer bizType, Long bizId, Long userId, AfterSalePermissionLevelEnum level);
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionServiceImpl.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionServiceImpl.java
new file mode 100644
index 0000000..219369e
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/AfterSalePermissionServiceImpl.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.aftersales.service.permission;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.permission.AfterSalePermissionDO;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.permission.AfterSalePermissionMapper;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum;
+import cn.iocoder.yudao.module.aftersales.service.permission.bo.AfterSalePermissionCreateReqBO;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 鍞悗妯″潡鏁版嵁鏉冮檺 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+public class AfterSalePermissionServiceImpl implements AfterSalePermissionService {
+
+ @Resource
+ private AfterSalePermissionMapper afterSalePermissionMapper;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createPermission(AfterSalePermissionCreateReqBO createReqBO) {
+ AfterSalePermissionDO permission = BeanUtils.toBean(createReqBO, AfterSalePermissionDO.class);
+ afterSalePermissionMapper.insert(permission);
+ return permission.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deletePermission(Integer bizType, Long bizId) {
+ afterSalePermissionMapper.deleteByBiz(bizType, bizId);
+ }
+
+ @Override
+ public List<AfterSalePermissionDO> getPermissionListByBiz(Integer bizType, Long bizId) {
+ return afterSalePermissionMapper.selectListByBiz(bizType, bizId);
+ }
+
+ @Override
+ public List<AfterSalePermissionDO> getPermissionListByBiz(Integer bizType, Collection<Long> bizIds) {
+ if (CollUtil.isEmpty(bizIds)) {
+ return Collections.emptyList();
+ }
+ return afterSalePermissionMapper.selectListByBiz(bizType, bizIds);
+ }
+
+ @Override
+ public boolean hasPermission(Integer bizType, Long bizId, Long userId, AfterSalePermissionLevelEnum level) {
+ List<AfterSalePermissionDO> permissionList = getPermissionListByBiz(bizType, bizId);
+ return permissionList.stream()
+ .anyMatch(p -> ObjUtil.equal(p.getUserId(), userId)
+ && ObjUtil.equal(p.getLevel(), level.getLevel()));
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/bo/AfterSalePermissionCreateReqBO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/bo/AfterSalePermissionCreateReqBO.java
new file mode 100644
index 0000000..13bc328
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/permission/bo/AfterSalePermissionCreateReqBO.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.aftersales.service.permission.bo;
+
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 鍞悗妯″潡鏁版嵁鏉冮檺鍒涘缓 Request BO
+ */
+@Data
+public class AfterSalePermissionCreateReqBO {
+
+ /**
+ * 鏁版嵁绫诲瀷锛屽叧鑱� {@link AfterSaleBizTypeEnum}
+ */
+ @NotNull(message = "鏁版嵁绫诲瀷涓嶈兘涓虹┖")
+ private Integer bizType;
+
+ /**
+ * 鏁版嵁缂栧彿
+ */
+ @NotNull(message = "鏁版嵁缂栧彿涓嶈兘涓虹┖")
+ private Long bizId;
+
+ /**
+ * 鐢ㄦ埛缂栧彿
+ */
+ @NotNull(message = "鐢ㄦ埛缂栧彿涓嶈兘涓虹┖")
+ private Long userId;
+
+ /**
+ * 鏉冮檺绾у埆锛屽叧鑱� {@link AfterSalePermissionLevelEnum}
+ */
+ @NotNull(message = "鏉冮檺绾у埆涓嶈兘涓虹┖")
+ private Integer level;
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairService.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairService.java
new file mode 100644
index 0000000..0c41603
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairService.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.aftersales.service.repair;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.repair.AfterSaleRepairDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍞悗缁翠慨璁板綍 Service 鎺ュ彛
+ */
+public interface AfterSaleRepairService {
+
+ /**
+ * 鍒涘缓缁翠慨璁板綍
+ *
+ * @param repairDO 缁翠慨淇℃伅
+ * @return 缂栧彿
+ */
+ Long createRepair(@Valid AfterSaleRepairDO repairDO);
+
+ /**
+ * 鏇存柊缁翠慨璁板綍
+ *
+ * @param repairDO 鏇存柊淇℃伅
+ */
+ void updateRepair(@Valid AfterSaleRepairDO repairDO);
+
+ /**
+ * 瀹屾垚缁翠慨
+ *
+ * @param id 缁翠慨璁板綍缂栧彿
+ */
+ void completeRepair(Long id);
+
+ /**
+ * 鑾峰緱缁翠慨璁板綍
+ *
+ * @param id 缂栧彿
+ * @return 缁翠慨璁板綍
+ */
+ AfterSaleRepairDO getRepair(Long id);
+
+ /**
+ * 鑾峰緱缁翠慨璁板綍鍒楄〃
+ *
+ * @param ids 缂栧彿鍒楄〃
+ * @return 缁翠慨璁板綍鍒楄〃
+ */
+ List<AfterSaleRepairDO> getRepairList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱缁翠慨璁板綍鍒嗛〉
+ *
+ * @param pageParam 鍒嗛〉鍙傛暟
+ * @param ticketId 鍏宠仈宸ュ崟缂栧彿
+ * @param status 缁翠慨鐘舵��
+ * @return 缁翠慨璁板綍鍒嗛〉
+ */
+ PageResult<AfterSaleRepairDO> getRepairPage(Object pageParam, Long ticketId, Integer status);
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairServiceImpl.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairServiceImpl.java
new file mode 100644
index 0000000..41a2163
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/repair/AfterSaleRepairServiceImpl.java
@@ -0,0 +1,110 @@
+package cn.iocoder.yudao.module.aftersales.service.repair;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.repair.AfterSaleRepairDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.repair.AfterSaleRepairMapper;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleRepairStatusEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.*;
+
+/**
+ * 鍞悗缁翠慨璁板綍 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class AfterSaleRepairServiceImpl implements AfterSaleRepairService {
+
+ @Resource
+ private AfterSaleRepairMapper repairMapper;
+ @Resource
+ private AfterSaleTicketMapper ticketMapper;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createRepair(AfterSaleRepairDO repairDO) {
+ // 鏍¢獙宸ュ崟瀛樺湪
+ AfterSaleTicketDO ticket = ticketMapper.selectById(repairDO.getTicketId());
+ if (ticket == null) {
+ throw exception(TICKET_NOT_EXISTS);
+ }
+
+ repairDO.setStatus(AfterSaleRepairStatusEnum.PENDING.getStatus());
+ repairMapper.insert(repairDO);
+
+ // 鏇存柊宸ュ崟鍏宠仈缁翠慨璁板綍
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId())
+ .setRepairId(repairDO.getId()));
+
+ return repairDO.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateRepair(AfterSaleRepairDO repairDO) {
+ validateRepairExists(repairDO.getId());
+ repairMapper.updateById(repairDO);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void completeRepair(Long id) {
+ AfterSaleRepairDO repair = validateRepairExists(id);
+ repairMapper.updateById(new AfterSaleRepairDO().setId(id)
+ .setStatus(AfterSaleRepairStatusEnum.COMPLETED.getStatus())
+ .setFinishTime(LocalDateTime.now()));
+ // 鏇存柊宸ュ崟鐘舵��
+ AfterSaleTicketDO ticket = ticketMapper.selectById(repair.getTicketId());
+ if (ticket != null) {
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId())
+ .setStatus(cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum.COMPLETED.getStatus())
+ .setResolvedTime(LocalDateTime.now()));
+ }
+ }
+
+ @Override
+ public AfterSaleRepairDO getRepair(Long id) {
+ return validateRepairExists(id);
+ }
+
+ @Override
+ public List<AfterSaleRepairDO> getRepairList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return List.of();
+ }
+ return repairMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public PageResult<AfterSaleRepairDO> getRepairPage(Object pageParam, Long ticketId, Integer status) {
+ if (pageParam instanceof PageParam) {
+ return repairMapper.selectPage((PageParam) pageParam, ticketId, status);
+ }
+ return repairMapper.selectPage(new PageParam(), ticketId, status);
+ }
+
+ // ========== 鍐呴儴鏂规硶 ==========
+
+ private AfterSaleRepairDO validateRepairExists(Long id) {
+ AfterSaleRepairDO repair = repairMapper.selectById(id);
+ if (repair == null) {
+ throw exception(REPAIR_NOT_EXISTS);
+ }
+ return repair;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnService.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnService.java
new file mode 100644
index 0000000..f3391f1
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnService.java
@@ -0,0 +1,92 @@
+package cn.iocoder.yudao.module.aftersales.service.returnobj;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnItemDO;
+import jakarta.validation.Valid;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍞悗閫�璐х敵璇� Service 鎺ュ彛
+ */
+public interface AfterSaleReturnService {
+
+ /**
+ * 鍒涘缓閫�璐х敵璇�
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 缂栧彿
+ */
+ Long createReturn(@Valid AfterSaleReturnSaveReqVO createReqVO, Long userId);
+
+ /**
+ * 瀹℃牳閫氳繃锛堣Е鍙� MES 閫�璐ф祦绋嬶級
+ *
+ * @param id 閫�璐х敵璇风紪鍙�
+ */
+ void approveReturn(Long id);
+
+ /**
+ * 鑾峰緱閫�璐х敵璇�
+ *
+ * @param id 缂栧彿
+ * @return 閫�璐х敵璇�
+ */
+ AfterSaleReturnDO getReturn(Long id);
+
+ /**
+ * 鑾峰緱閫�璐х敵璇峰垪琛�
+ *
+ * @param ids 缂栧彿鍒楄〃
+ * @return 閫�璐х敵璇峰垪琛�
+ */
+ List<AfterSaleReturnDO> getReturnList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱閫�璐х敵璇峰垎椤�
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 閫�璐х敵璇峰垎椤�
+ */
+ PageResult<AfterSaleReturnDO> getReturnPage(AfterSaleReturnPageReqVO pageReqVO, Long userId);
+
+ /**
+ * 鑾峰緱閫�璐ф槑缁嗚鍒楄〃
+ *
+ * @param returnId 閫�璐х敵璇风紪鍙�
+ * @return 鏄庣粏琛屽垪琛�
+ */
+ List<AfterSaleReturnItemDO> getReturnItemListByReturnId(Long returnId);
+
+ /**
+ * MES 璐ㄦ瀹屾垚鍥炶皟
+ *
+ * @param returnId 閫�璐х敵璇风紪鍙�
+ * @param qualityResult 璐ㄦ缁撹
+ * @param reportUrl 璐ㄦ鎶ュ憡 URL
+ */
+ void onQualityFinished(Long returnId, String qualityResult, String reportUrl);
+
+ /**
+ * MES 閫�璐у叆搴撳畬鎴愬洖璋�
+ *
+ * @param returnId 閫�璐х敵璇风紪鍙�
+ */
+ void onInboundFinished(Long returnId);
+
+ /**
+ * ERP 閫�娆惧畬鎴愬洖璋�
+ *
+ * @param returnId 閫�璐х敵璇风紪鍙�
+ * @param actualRefundPrice 瀹為��閲戦
+ */
+ void onRefundCompleted(Long returnId, BigDecimal actualRefundPrice);
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnServiceImpl.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnServiceImpl.java
new file mode 100644
index 0000000..ed5fcd6
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnServiceImpl.java
@@ -0,0 +1,221 @@
+package cn.iocoder.yudao.module.aftersales.service.returnobj;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnItemDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnItemMapper;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnMapper;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper;
+import cn.iocoder.yudao.module.aftersales.dal.redis.no.AfterSaleNoRedisDAO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleReturnStatusEnum;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum;
+import cn.iocoder.yudao.module.aftersales.service.permission.AfterSalePermissionService;
+import cn.iocoder.yudao.module.aftersales.service.permission.bo.AfterSalePermissionCreateReqBO;
+import cn.iocoder.yudao.module.mes.api.returnsales.MesWmReturnSalesApi;
+import cn.iocoder.yudao.module.mes.api.returnsales.dto.MesWmReturnSalesCreateReqDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.*;
+
+/**
+ * 鍞悗閫�璐х敵璇� Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class AfterSaleReturnServiceImpl implements AfterSaleReturnService {
+
+ @Resource
+ private AfterSaleReturnMapper returnMapper;
+ @Resource
+ private AfterSaleReturnItemMapper returnItemMapper;
+ @Resource
+ private AfterSaleTicketMapper ticketMapper;
+
+ @Resource
+ private AfterSaleNoRedisDAO noRedisDAO;
+
+ @Resource
+ private AfterSalePermissionService permissionService;
+
+ @Resource
+ private MesWmReturnSalesApi mesWmReturnSalesApi;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createReturn(AfterSaleReturnSaveReqVO createReqVO, Long userId) {
+ // 1. 鏍¢獙宸ュ崟瀛樺湪
+ AfterSaleTicketDO ticket = ticketMapper.selectById(createReqVO.getTicketId());
+ if (ticket == null) {
+ throw exception(TICKET_NOT_EXISTS);
+ }
+
+ // 2. 鐢熸垚鍗曞彿
+ String no = noRedisDAO.generate(AfterSaleNoRedisDAO.RETURN_NO_PREFIX);
+ if (returnMapper.selectByNo(no) != null) {
+ throw exception(RETURN_NO_EXISTS);
+ }
+
+ // 3. 璁$畻搴旈��鎬婚噾棰�
+ BigDecimal totalReturnPrice = BigDecimal.ZERO;
+ if (CollUtil.isNotEmpty(createReqVO.getItems())) {
+ totalReturnPrice = createReqVO.getItems().stream()
+ .map(item -> item.getReturnPrice() != null ? item.getReturnPrice() : BigDecimal.ZERO)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ // 4. 鎻掑叆閫�璐х敵璇�
+ AfterSaleReturnDO returnDO = BeanUtils.toBean(createReqVO, AfterSaleReturnDO.class)
+ .setNo(no)
+ .setTotalReturnPrice(totalReturnPrice)
+ .setStatus(AfterSaleReturnStatusEnum.DRAFT.getStatus());
+ returnMapper.insert(returnDO);
+
+ // 5. 鎻掑叆閫�璐ф槑缁嗚
+ if (CollUtil.isNotEmpty(createReqVO.getItems())) {
+ createReqVO.getItems().forEach(item -> {
+ AfterSaleReturnItemDO itemDO = BeanUtils.toBean(item, AfterSaleReturnItemDO.class);
+ itemDO.setReturnId(returnDO.getId());
+ returnItemMapper.insert(itemDO);
+ });
+ }
+
+ // 6. 鍒涘缓鏁版嵁鏉冮檺
+ AfterSalePermissionCreateReqBO permBO = new AfterSalePermissionCreateReqBO();
+ permBO.setBizType(AfterSaleBizTypeEnum.AFTER_SALE_RETURN.getType());
+ permBO.setBizId(returnDO.getId());
+ permBO.setUserId(userId);
+ permBO.setLevel(AfterSalePermissionLevelEnum.OWNER.getLevel());
+ permissionService.createPermission(permBO);
+
+ // 7. 鏇存柊宸ュ崟鐘舵�佷负寰呴��璐�
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId())
+ .setReturnId(returnDO.getId())
+ .setStatus(AfterSaleTicketStatusEnum.WAITING_RETURN.getStatus()));
+
+ return returnDO.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void approveReturn(Long id) {
+ // 1. 鏍¢獙閫�璐х敵璇�
+ AfterSaleReturnDO returnDO = validateReturnExists(id);
+ if (!AfterSaleReturnStatusEnum.DRAFT.getStatus().equals(returnDO.getStatus())) {
+ throw exception(RETURN_APPROVE_FAIL);
+ }
+
+ // 2. 鑾峰彇鍏宠仈宸ュ崟锛堢敤浜庤幏鍙栭攢鍞鍗曞彿绛夊啑浣欎俊鎭級
+ AfterSaleTicketDO ticket = ticketMapper.selectById(returnDO.getTicketId());
+
+ // 3. 璋冪敤 MES 鍒涘缓閿�鍞��璐у崟
+ MesWmReturnSalesCreateReqDTO mesReqDTO = new MesWmReturnSalesCreateReqDTO();
+ mesReqDTO.setCode(returnDO.getNo());
+ mesReqDTO.setName("鍞悗閫�璐�-" + returnDO.getNo());
+ mesReqDTO.setSaleOrderId(returnDO.getSaleOrderId());
+ mesReqDTO.setSalesOrderCode(ticket != null ? ticket.getSaleOrderNo() : null);
+ mesReqDTO.setClientId(returnDO.getCustomerId());
+ mesReqDTO.setReturnDate(LocalDate.now());
+ mesReqDTO.setReturnReason(returnDO.getReturnReason());
+ mesReqDTO.setRemark("鍞悗宸ュ崟閫�璐� - 宸ュ崟鍙�: " + (ticket != null ? ticket.getNo() : ""));
+ mesReqDTO.setAfterSaleReturnId(returnDO.getId());
+ Long mesReturnId = mesWmReturnSalesApi.createReturnSales(mesReqDTO);
+
+ // 4. 鏇存柊閫�璐х敵璇风姸鎬�
+ returnMapper.updateById(new AfterSaleReturnDO().setId(id)
+ .setStatus(AfterSaleReturnStatusEnum.APPROVED.getStatus())
+ .setMesReturnId(mesReturnId)
+ .setMesReturnCode(returnDO.getNo())
+ .setAuditTime(LocalDateTime.now()));
+ }
+
+ @Override
+ public AfterSaleReturnDO getReturn(Long id) {
+ return validateReturnExists(id);
+ }
+
+ @Override
+ public List<AfterSaleReturnDO> getReturnList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return List.of();
+ }
+ return returnMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public PageResult<AfterSaleReturnDO> getReturnPage(AfterSaleReturnPageReqVO pageReqVO, Long userId) {
+ return returnMapper.selectPage(pageReqVO, userId, pageReqVO.getSceneType(),
+ pageReqVO.getNo(), pageReqVO.getTicketId(),
+ pageReqVO.getCustomerId(), pageReqVO.getStatus());
+ }
+
+ @Override
+ public List<AfterSaleReturnItemDO> getReturnItemListByReturnId(Long returnId) {
+ return returnItemMapper.selectListByReturnId(returnId);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void onQualityFinished(Long returnId, String qualityResult, String reportUrl) {
+ AfterSaleReturnDO returnDO = validateReturnExists(returnId);
+ returnMapper.updateById(new AfterSaleReturnDO().setId(returnId)
+ .setQualityResult(qualityResult)
+ .setQualityReportUrl(reportUrl)
+ .setStatus(AfterSaleReturnStatusEnum.INSPECTED.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void onInboundFinished(Long returnId) {
+ AfterSaleReturnDO returnDO = validateReturnExists(returnId);
+ returnMapper.updateById(new AfterSaleReturnDO().setId(returnId)
+ .setStatus(AfterSaleReturnStatusEnum.INBOUND.getStatus())
+ .setReturnInboundTime(LocalDateTime.now()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void onRefundCompleted(Long returnId, BigDecimal actualRefundPrice) {
+ AfterSaleReturnDO returnDO = validateReturnExists(returnId);
+ returnMapper.updateById(new AfterSaleReturnDO().setId(returnId)
+ .setActualRefundPrice(actualRefundPrice)
+ .setStatus(AfterSaleReturnStatusEnum.REFUNDED.getStatus())
+ .setRefundTime(LocalDateTime.now()));
+
+ // 鏇存柊鍏宠仈宸ュ崟鐘舵�佷负寰呴��娆�/鎹㈣揣瀹屾垚
+ AfterSaleTicketDO ticket = ticketMapper.selectById(returnDO.getTicketId());
+ if (ticket != null) {
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId())
+ .setStatus(AfterSaleTicketStatusEnum.WAITING_REFUND.getStatus()));
+ }
+ }
+
+ // ========== 鍐呴儴鏂规硶 ==========
+
+ private AfterSaleReturnDO validateReturnExists(Long id) {
+ AfterSaleReturnDO returnDO = returnMapper.selectById(id);
+ if (returnDO == null) {
+ throw exception(RETURN_NOT_EXISTS);
+ }
+ return returnDO;
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketService.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketService.java
new file mode 100644
index 0000000..7c3a9d0
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketService.java
@@ -0,0 +1,113 @@
+package cn.iocoder.yudao.module.aftersales.service.ticket;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO;
+import jakarta.validation.Valid;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 鍞悗宸ュ崟 Service 鎺ュ彛
+ */
+public interface AfterSaleTicketService {
+
+ /**
+ * 鍒涘缓宸ュ崟
+ *
+ * @param createReqVO 鍒涘缓淇℃伅
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 缂栧彿
+ */
+ Long createTicket(@Valid AfterSaleTicketSaveReqVO createReqVO, Long userId);
+
+ /**
+ * 鏇存柊宸ュ崟
+ *
+ * @param updateReqVO 鏇存柊淇℃伅
+ */
+ void updateTicket(@Valid AfterSaleTicketSaveReqVO updateReqVO);
+
+ /**
+ * 鍒犻櫎宸ュ崟
+ *
+ * @param id 缂栧彿
+ */
+ void deleteTicket(Long id);
+
+ /**
+ * 鑾峰緱宸ュ崟
+ *
+ * @param id 缂栧彿
+ * @return 宸ュ崟
+ */
+ AfterSaleTicketDO getTicket(Long id);
+
+ /**
+ * 鑾峰緱宸ュ崟鍒楄〃
+ *
+ * @param ids 缂栧彿鍒楄〃
+ * @return 宸ュ崟鍒楄〃
+ */
+ List<AfterSaleTicketDO> getTicketList(Collection<Long> ids);
+
+ /**
+ * 鑾峰緱宸ュ崟鍒嗛〉
+ *
+ * @param pageReqVO 鍒嗛〉鏌ヨ
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @return 宸ュ崟鍒嗛〉
+ */
+ PageResult<AfterSaleTicketDO> getTicketPage(AfterSaleTicketPageReqVO pageReqVO, Long userId);
+
+ /**
+ * 鑾峰緱宸ュ崟鏄庣粏琛屽垪琛�
+ *
+ * @param ticketId 宸ュ崟缂栧彿
+ * @return 鏄庣粏琛屽垪琛�
+ */
+ List<AfterSaleTicketItemDO> getTicketItemListByTicketId(Long ticketId);
+
+ /**
+ * 鍒嗛厤澶勭悊浜�
+ *
+ * @param id 宸ュ崟缂栧彿
+ * @param handlerUserId 澶勭悊浜虹敤鎴风紪鍙�
+ */
+ void assignTicket(Long id, Long handlerUserId);
+
+ /**
+ * 闂鍒ゅ畾锛堢‘瀹氶棶棰樼被鍨嬨�佸垎绫汇�佷弗閲嶇▼搴︼級
+ *
+ * @param id 宸ュ崟缂栧彿
+ * @param issueType 闂绫诲瀷
+ * @param issueCategory 闂鍒嗙被
+ * @param severityLevel 涓ラ噸绋嬪害
+ */
+ void classifyTicket(Long id, Integer issueType, String issueCategory, Integer severityLevel);
+
+ /**
+ * 瀹岀粨宸ュ崟
+ *
+ * @param id 宸ュ崟缂栧彿
+ */
+ void resolveTicket(Long id);
+
+ /**
+ * 鍏抽棴宸ュ崟
+ *
+ * @param id 宸ュ崟缂栧彿
+ */
+ void closeTicket(Long id);
+
+ /**
+ * 鍙栨秷宸ュ崟
+ *
+ * @param id 宸ュ崟缂栧彿
+ */
+ void cancelTicket(Long id);
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java
new file mode 100644
index 0000000..a076b58
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java
@@ -0,0 +1,212 @@
+package cn.iocoder.yudao.module.aftersales.service.ticket;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketPageReqVO;
+import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketSaveReqVO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketItemMapper;
+import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper;
+import cn.iocoder.yudao.module.aftersales.dal.redis.no.AfterSaleNoRedisDAO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleTicketStatusEnum;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum;
+import cn.iocoder.yudao.module.aftersales.service.permission.AfterSalePermissionService;
+import cn.iocoder.yudao.module.aftersales.service.permission.bo.AfterSalePermissionCreateReqBO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDateTime;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.*;
+
+/**
+ * 鍞悗宸ュ崟 Service 瀹炵幇绫�
+ */
+@Service
+@Validated
+@Slf4j
+public class AfterSaleTicketServiceImpl implements AfterSaleTicketService {
+
+ @Resource
+ private AfterSaleTicketMapper ticketMapper;
+ @Resource
+ private AfterSaleTicketItemMapper ticketItemMapper;
+
+ @Resource
+ private AfterSaleNoRedisDAO noRedisDAO;
+
+ @Resource
+ private AfterSalePermissionService permissionService;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createTicket(AfterSaleTicketSaveReqVO createReqVO, Long userId) {
+ // 1. 鐢熸垚鍗曞彿
+ String no = noRedisDAO.generate(AfterSaleNoRedisDAO.TICKET_NO_PREFIX);
+ if (ticketMapper.selectByNo(no) != null) {
+ throw exception(TICKET_NO_EXISTS);
+ }
+
+ // 2. 鎻掑叆宸ュ崟
+ AfterSaleTicketDO ticket = BeanUtils.toBean(createReqVO, AfterSaleTicketDO.class)
+ .setNo(no)
+ .setStatus(AfterSaleTicketStatusEnum.DRAFT.getStatus())
+ .setOwnerUserId(userId);
+ ticketMapper.insert(ticket);
+
+ // 3. 鎻掑叆鏄庣粏琛�
+ if (CollUtil.isNotEmpty(createReqVO.getItems())) {
+ createReqVO.getItems().forEach(item -> {
+ AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class);
+ itemDO.setTicketId(ticket.getId());
+ ticketItemMapper.insert(itemDO);
+ });
+ }
+
+ // 4. 鍒涘缓鏁版嵁鏉冮檺锛堣礋璐d汉锛�
+ AfterSalePermissionCreateReqBO permBO = new AfterSalePermissionCreateReqBO();
+ permBO.setBizType(AfterSaleBizTypeEnum.AFTER_SALE_TICKET.getType());
+ permBO.setBizId(ticket.getId());
+ permBO.setUserId(userId);
+ permBO.setLevel(AfterSalePermissionLevelEnum.OWNER.getLevel());
+ permissionService.createPermission(permBO);
+
+ return ticket.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateTicket(AfterSaleTicketSaveReqVO updateReqVO) {
+ // 1. 鏍¢獙瀛樺湪
+ AfterSaleTicketDO ticket = validateTicketExists(updateReqVO.getId());
+ validateTicketStatus(ticket, AfterSaleTicketStatusEnum.DRAFT);
+
+ // 2. 鏇存柊宸ュ崟
+ AfterSaleTicketDO updateObj = BeanUtils.toBean(updateReqVO, AfterSaleTicketDO.class);
+ ticketMapper.updateById(updateObj);
+
+ // 3. 鏇存柊鏄庣粏琛岋紙鍏堝垹鍚庡锛�
+ ticketItemMapper.deleteByTicketId(ticket.getId());
+ if (CollUtil.isNotEmpty(updateReqVO.getItems())) {
+ updateReqVO.getItems().forEach(item -> {
+ AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class);
+ itemDO.setTicketId(ticket.getId());
+ ticketItemMapper.insert(itemDO);
+ });
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteTicket(Long id) {
+ AfterSaleTicketDO ticket = validateTicketExists(id);
+ validateTicketStatus(ticket, AfterSaleTicketStatusEnum.DRAFT, AfterSaleTicketStatusEnum.CANCELED);
+ // 杞垹闄�
+ ticketMapper.deleteById(id);
+ ticketItemMapper.deleteByTicketId(id);
+ permissionService.deletePermission(AfterSaleBizTypeEnum.AFTER_SALE_TICKET.getType(), id);
+ }
+
+ @Override
+ public AfterSaleTicketDO getTicket(Long id) {
+ return validateTicketExists(id);
+ }
+
+ @Override
+ public List<AfterSaleTicketDO> getTicketList(Collection<Long> ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return List.of();
+ }
+ return ticketMapper.selectBatchIds(ids);
+ }
+
+ @Override
+ public PageResult<AfterSaleTicketDO> getTicketPage(AfterSaleTicketPageReqVO pageReqVO, Long userId) {
+ return ticketMapper.selectPage(pageReqVO, userId, pageReqVO.getSceneType(),
+ pageReqVO.getNo(), pageReqVO.getName(),
+ pageReqVO.getCustomerId(), pageReqVO.getSaleOrderId(),
+ pageReqVO.getStatus(), pageReqVO.getIssueType());
+ }
+
+ @Override
+ public List<AfterSaleTicketItemDO> getTicketItemListByTicketId(Long ticketId) {
+ return ticketItemMapper.selectListByTicketId(ticketId);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void assignTicket(Long id, Long handlerUserId) {
+ AfterSaleTicketDO ticket = validateTicketExists(id);
+ validateTicketStatus(ticket, AfterSaleTicketStatusEnum.DRAFT, AfterSaleTicketStatusEnum.PROCESSING);
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(id).setHandlerUserId(handlerUserId)
+ .setStatus(AfterSaleTicketStatusEnum.PROCESSING.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void classifyTicket(Long id, Integer issueType, String issueCategory, Integer severityLevel) {
+ AfterSaleTicketDO ticket = validateTicketExists(id);
+ validateTicketStatus(ticket, AfterSaleTicketStatusEnum.PROCESSING);
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(id)
+ .setIssueType(issueType).setIssueCategory(issueCategory).setSeverityLevel(severityLevel));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void resolveTicket(Long id) {
+ AfterSaleTicketDO ticket = validateTicketExists(id);
+ validateTicketStatus(ticket, AfterSaleTicketStatusEnum.PROCESSING,
+ AfterSaleTicketStatusEnum.WAITING_RETURN, AfterSaleTicketStatusEnum.WAITING_REFUND);
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(id)
+ .setStatus(AfterSaleTicketStatusEnum.COMPLETED.getStatus())
+ .setResolvedTime(LocalDateTime.now()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void closeTicket(Long id) {
+ AfterSaleTicketDO ticket = validateTicketExists(id);
+ validateTicketStatus(ticket, AfterSaleTicketStatusEnum.COMPLETED);
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(id)
+ .setStatus(AfterSaleTicketStatusEnum.CLOSED.getStatus())
+ .setClosedTime(LocalDateTime.now()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void cancelTicket(Long id) {
+ AfterSaleTicketDO ticket = validateTicketExists(id);
+ validateTicketStatus(ticket, AfterSaleTicketStatusEnum.DRAFT, AfterSaleTicketStatusEnum.PROCESSING);
+ ticketMapper.updateById(new AfterSaleTicketDO().setId(id)
+ .setStatus(AfterSaleTicketStatusEnum.CANCELED.getStatus()));
+ }
+
+ // ========== 鍐呴儴鏂规硶 ==========
+
+ private AfterSaleTicketDO validateTicketExists(Long id) {
+ AfterSaleTicketDO ticket = ticketMapper.selectById(id);
+ if (ticket == null) {
+ throw exception(TICKET_NOT_EXISTS);
+ }
+ return ticket;
+ }
+
+ private void validateTicketStatus(AfterSaleTicketDO ticket, AfterSaleTicketStatusEnum... allowedStatuses) {
+ for (AfterSaleTicketStatusEnum status : allowedStatuses) {
+ if (status.getStatus().equals(ticket.getStatus())) {
+ return;
+ }
+ }
+ throw exception(TICKET_STATUS_ERROR);
+ }
+
+}
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSalePermissionUtils.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSalePermissionUtils.java
new file mode 100644
index 0000000..b8da995
--- /dev/null
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/util/AfterSalePermissionUtils.java
@@ -0,0 +1,94 @@
+package cn.iocoder.yudao.module.aftersales.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
+import cn.iocoder.yudao.module.aftersales.dal.dataobject.permission.AfterSalePermissionDO;
+import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSalePermissionLevelEnum;
+import cn.iocoder.yudao.module.aftersales.enums.permission.AfterSaleSceneTypeEnum;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties;
+import com.github.yulichang.wrapper.MPJLambdaWrapper;
+import org.springframework.util.Assert;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * 鍞悗妯″潡鏁版嵁鏉冮檺宸ュ叿绫�
+ */
+public class AfterSalePermissionUtils {
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鏄惁鏄敭鍚庣鐞嗗憳
+ *
+ * @return 鏄�/鍚�
+ */
+ public static boolean isAftersaleAdmin() {
+ PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
+ return permissionApi.hasAnyRoles(getLoginUserId(), "aftersales_admin");
+ }
+
+ /**
+ * 鏍¢獙鐢ㄦ埛鏄惁鏈夋煡鐪嬪叏閮ㄥ敭鍚庢暟鎹殑鏉冮檺
+ */
+ private static boolean hasViewAllPermission() {
+ PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
+ return permissionApi.hasAnyPermissions(getLoginUserId(), "aftersales:view-all");
+ }
+
+ /**
+ * 鏋勯�犲敭鍚庢ā鍧楁暟鎹�愬垎椤点�戞煡璇㈡潯浠�
+ *
+ * @param query 杩炶〃鏌ヨ瀵硅薄
+ * @param bizType 鏁版嵁绫诲瀷 {@link AfterSaleBizTypeEnum}
+ * @param bizId 鏁版嵁缂栧彿
+ * @param userId 鐢ㄦ埛缂栧彿
+ * @param sceneType 鍦烘櫙绫诲瀷
+ */
+ public static <T extends MPJLambdaWrapper<?>, S> void appendPermissionCondition(T query, Integer bizType,
+ SFunction<S, ?> bizId,
+ Long userId, Integer sceneType) {
+ MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class);
+ final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id";
+ // 鍦烘櫙涓�锛氭垜璐熻矗鐨勬暟鎹�
+ if (AfterSaleSceneTypeEnum.isOwner(sceneType)) {
+ query.eq(ownerUserIdField, userId);
+ }
+ // 鍦烘櫙浜岋細鎴戝弬涓庣殑鏁版嵁锛堟垜鏈夎鎴栧啓鏉冮檺锛屽苟涓斾笉鏄礋璐d汉锛�
+ if (AfterSaleSceneTypeEnum.isInvolved(sceneType)) {
+ if (isAftersaleAdmin()) {
+ return;
+ }
+ query.innerJoin(AfterSalePermissionDO.class, on -> on.eq(AfterSalePermissionDO::getBizType, bizType)
+ .eq(AfterSalePermissionDO::getBizId, bizId)
+ .in(AfterSalePermissionDO::getLevel, AfterSalePermissionLevelEnum.READ.getLevel(),
+ AfterSalePermissionLevelEnum.WRITE.getLevel())
+ .eq(AfterSalePermissionDO::getUserId, userId));
+ query.ne(ownerUserIdField, userId);
+ }
+ // 鍦烘櫙涓夛細涓嬪睘璐熻矗鐨勬暟鎹紙涓嬪睘鏄礋璐d汉锛�
+ if (AfterSaleSceneTypeEnum.isSubordinate(sceneType)) {
+ AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class);
+ List<AdminUserRespDTO> subordinateUsers = adminUserApi.getUserListBySubordinate(userId);
+ if (CollUtil.isEmpty(subordinateUsers)) {
+ query.eq(ownerUserIdField, -1);
+ } else {
+ query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId));
+ }
+ }
+ // 鍦烘櫙鍥涳細鍏ㄩ儴鏁版嵁锛堥渶瑕佹湁 view-all 鏉冮檺鎴栧敭鍚庣鐞嗗憳锛�
+ if (AfterSaleSceneTypeEnum.isAll(sceneType)) {
+ if (isAftersaleAdmin() || hasViewAllPermission()) {
+ return;
+ }
+ query.eq(ownerUserIdField, userId);
+ }
+ }
+
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
index e6a1991..25de6e6 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -51,7 +51,11 @@
default PageResult<CrmCustomerDO> selectPage(CrmCustomerPageReqVO pageReqVO, Long ownerUserId) {
MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>();
// 鎷兼帴鏁版嵁鏉冮檺鐨勬煡璇㈡潯浠�
- if (Boolean.TRUE.equals(pageReqVO.getPool())) {
+ if (CrmSceneTypeEnum.isAll(pageReqVO.getSceneType())) {
+ // 鍦烘櫙鍥涳細鍏ㄩ儴鏁版嵁锛屼笉鍖哄垎鍏捣/闈炲叕娴凤紝鐢� appendPermissionCondition 缁熶竴澶勭悊
+ CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
+ CrmCustomerDO::getId, ownerUserId, pageReqVO.getSceneType());
+ } else if (Boolean.TRUE.equals(pageReqVO.getPool())) {
query.isNull(CrmCustomerDO::getOwnerUserId);
} else {
CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(),
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java
index 9a1a420..d129d02 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmSceneTypeEnum.java
@@ -18,7 +18,8 @@
OWNER(1, "鎴戣礋璐g殑"),
INVOLVED(2, "鎴戝弬涓庣殑"),
- SUBORDINATE(3, "涓嬪睘璐熻矗鐨�");
+ SUBORDINATE(3, "涓嬪睘璐熻矗鐨�"),
+ ALL(4, "鍏ㄩ儴");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(CrmSceneTypeEnum::getType).toArray(Integer[]::new);
@@ -43,6 +44,10 @@
return ObjUtil.equal(SUBORDINATE.getType(), type);
}
+ public static boolean isAll(Integer type) {
+ return ObjUtil.equal(ALL.getType(), type);
+ }
+
@Override
public Integer[] array() {
return ARRAYS;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java
index c97266b..24740c9 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java
@@ -37,6 +37,16 @@
}
/**
+ * 鏍¢獙鐢ㄦ埛鏄惁鏈夋煡鐪嬪叏閮ㄥ鎴风殑鏉冮檺
+ *
+ * @return 鏄�/鍚�
+ */
+ private static boolean hasCustomerViewAllPermission() {
+ PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
+ return permissionApi.hasAnyPermissions(getLoginUserId(), "crm:customer:view-all");
+ }
+
+ /**
* 鏋勯�� CRM 鏁版嵁绫诲瀷鏁版嵁銆愬垎椤点�戞煡璇㈡潯浠�
*
* @param query 杩炶〃鏌ヨ瀵硅薄
@@ -79,6 +89,14 @@
query.in(ownerUserIdField, convertSet(subordinateUsers, AdminUserRespDTO::getId));
}
}
+ // 鍦烘櫙鍥涳細鍏ㄩ儴鏁版嵁锛堥渶瑕佹湁 view-all 鏉冮檺鎴� CRM 绠$悊鍛橈級
+ if (CrmSceneTypeEnum.isAll(sceneType)) {
+ if (isCrmAdmin() || hasCustomerViewAllPermission()) {
+ return; // 璺宠繃杩囨护锛屾煡鐪嬪叏閮�
+ }
+ // 鏃犳潈闄愬垯闄嶇骇涓�"鎴戣礋璐g殑"
+ query.eq(ownerUserIdField, userId);
+ }
}
/**
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
index 0107c1f..3debc10 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -5,6 +5,7 @@
import lombok.Data;
import java.math.BigDecimal;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@@ -119,7 +120,7 @@
private BigDecimal taxPrice;
@Schema(description = "闇�姹傛棩鏈�")
- private LocalDateTime demandTime;
+ private LocalDate demandTime;
@Schema(description = "澶囨敞", example = "澶囨敞")
private String remark;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
index 2019f86..d12da09 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
@@ -7,6 +7,7 @@
import lombok.Data;
import java.math.BigDecimal;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@@ -70,7 +71,7 @@
private BigDecimal taxPercent;
@Schema(description = "闇�姹傛棩鏈�")
- private LocalDateTime demandTime;
+ private LocalDate demandTime;
@Schema(description = "澶囨敞", example = "澶囨敞")
private String remark;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
index 7005b01..a96501b 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -19,6 +19,7 @@
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
+import cn.iocoder.yudao.module.mes.api.productsales.MesWmProductSalesLineApi;
import cn.iocoder.yudao.module.mes.api.salesnotice.MesWmSalesNoticeApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -60,6 +61,8 @@
private AdminUserApi adminUserApi;
@Resource
private MesWmSalesNoticeApi salesNoticeApi;
+ @Resource
+ private MesWmProductSalesLineApi productSalesLineApi;
@Resource
private MesProMpsApi mpsApi;
@Resource
@@ -128,10 +131,13 @@
List<ErpSaleOrderItemDO> saleOrderItemList = saleOrderService.getSaleOrderItemListByOrderId(id);
Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductId));
+ Map<Long, String> batchCodeMap = productSalesLineApi.getBatchCodesBySaleOrderItemIds(
+ convertSet(saleOrderItemList, ErpSaleOrderItemDO::getId));
return success(BeanUtils.toBean(saleOrder, ErpSaleOrderRespVO.class, saleOrderVO ->
saleOrderVO.setItems(BeanUtils.toBean(saleOrderItemList, ErpSaleOrderRespVO.Item.class, item -> {
// 搴撳瓨淇℃伅鏆備笉鏌ヨ锛堜粨鍌ㄧ墿娴佹ā鍧楋級
item.setStockCount(BigDecimal.ZERO);
+ item.setBatchCode(batchCodeMap.get(item.getId()));
MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
.setProductBarCode(mdmItem.getBarCode()));
})).setAttachmentList(storageAttachmentApi.listAttachments("erp_sale_order", id))));
@@ -177,7 +183,10 @@
// 1.5 鍙戣揣閫氱煡鍗曚俊鎭�
Map<Long, Boolean> salesNoticeMap = salesNoticeApi.checkSalesNoticeBySaleOrderIds(
convertSet(pageResult.getList(), ErpSaleOrderDO::getId));
- // 1.6 妫�鏌ユ槸鍚﹀彲浠ョ敓鎴愬彂璐ч�氱煡鍗曪紙闇�瑕佺敓浜х殑鏄庣粏瀵瑰簲鐨� MPS 鏄惁宸插畬鎴愶級
+ // 1.6 鍑哄簱鎵瑰彿
+ Map<Long, String> batchCodeMap = productSalesLineApi.getBatchCodesBySaleOrderItemIds(
+ convertSet(saleOrderItemList, ErpSaleOrderItemDO::getId));
+ // 1.7 妫�鏌ユ槸鍚﹀彲浠ョ敓鎴愬彂璐ч�氱煡鍗曪紙闇�瑕佺敓浜х殑鏄庣粏瀵瑰簲鐨� MPS 鏄惁宸插畬鎴愶級
// 绛涢�夊嚭闇�瑕佺敓浜х殑鏄庣粏ID
List<Long> needProductionItemIds = saleOrderItemList.stream()
.filter(item -> item.getNeedProduction() != null && item.getNeedProduction() == 1)
@@ -214,8 +223,11 @@
// 2. 寮�濮嬫嫾鎺�
return BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class, saleOrder -> {
saleOrder.setItems(BeanUtils.toBean(saleOrderItemMap.get(saleOrder.getId()), ErpSaleOrderRespVO.Item.class,
- item -> MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
- .setProductBarCode(mdmItem.getBarCode()))));
+ item -> {
+ item.setBatchCode(batchCodeMap.get(item.getId()));
+ MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
+ .setProductBarCode(mdmItem.getBarCode()));
+ }));
saleOrder.setProductNames(CollUtil.join(saleOrder.getItems(), "锛�", ErpSaleOrderRespVO.Item::getProductName));
MapUtils.findAndThen(customerMap, saleOrder.getCustomerId(), supplier -> saleOrder.setCustomerName(supplier.getName()));
MapUtils.findAndThen(userMap, Long.parseLong(saleOrder.getCreator()), user -> saleOrder.setCreatorName(user.getNickname()));
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java
index 8d1e313..7f6bfc1 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderPageReqVO.java
@@ -33,6 +33,15 @@
@Schema(description = "閿�鍞姸鎬�", example = "2")
private Integer status;
+ @Schema(description = "鍑哄簱鐘舵��", example = "3")
+ private Integer outStatus;
+
+ @Schema(description = "鐢熶骇鐘舵��", example = "0")
+ private Integer productionStatus;
+
+ @Schema(description = "鏄惁闇�瑕佺敓浜�", example = "1")
+ private Integer needProduction;
+
@Schema(description = "鍒涘缓鑰�")
private String creator;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
index f28a6e2..4070879 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
@@ -164,6 +164,11 @@
@Schema(description = "鍏宠仈鐨� MPS 缂栧彿", example = "1024")
private Long mpsId;
+ // ========== 鍑哄簱鎵瑰彿 ==========
+
+ @Schema(description = "鍑哄簱鎵瑰彿", example = "B2026001銆丅2026002")
+ private String batchCode;
+
// ========== 鍏宠仈瀛楁 ==========
@Schema(description = "浜у搧鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "宸у厠鍔�")
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
index 40ac455..f69cae5 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
@@ -7,7 +7,7 @@
import lombok.*;
import java.math.BigDecimal;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
/**
* ERP 閲囪喘鐢宠鏄庣粏 DO
@@ -71,7 +71,7 @@
/**
* 闇�姹傛棩鏈�
*/
- private LocalDateTime demandTime;
+ private LocalDate demandTime;
/**
* 澶囨敞
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
index 7382197..2e1dcb9 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/sale/ErpSaleOrderMapper.java
@@ -27,12 +27,15 @@
.eqIfPresent(ErpSaleOrderDO::getCustomerId, reqVO.getCustomerId())
.betweenIfPresent(ErpSaleOrderDO::getOrderTime, reqVO.getOrderTime())
.eqIfPresent(ErpSaleOrderDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(ErpSaleOrderDO::getOutStatus, reqVO.getOutStatus())
+ .eqIfPresent(ErpSaleOrderDO::getProductionStatus, reqVO.getProductionStatus())
.likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark())
.eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator())
.orderByDesc(ErpSaleOrderDO::getId);
- if (reqVO.getProductId() != null) {
+ if (reqVO.getProductId() != null || reqVO.getNeedProduction() != null) {
query.leftJoin(ErpSaleOrderItemDO.class, ErpSaleOrderItemDO::getOrderId, ErpSaleOrderDO::getId)
.eq(reqVO.getProductId() != null, ErpSaleOrderItemDO::getProductId, reqVO.getProductId())
+ .eq(reqVO.getNeedProduction() != null, ErpSaleOrderItemDO::getNeedProduction, reqVO.getNeedProduction())
.groupBy(ErpSaleOrderDO::getId); // 閬垮厤 1 瀵瑰鏌ヨ锛屼骇鐢熺浉鍚岀殑 1
}
return selectJoinPage(reqVO, ErpSaleOrderDO.class, query);
@@ -47,6 +50,8 @@
.eqIfPresent(ErpSaleOrderDO::getCustomerId, reqVO.getCustomerId())
.betweenIfPresent(ErpSaleOrderDO::getOrderTime, reqVO.getOrderTime())
.eqIfPresent(ErpSaleOrderDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(ErpSaleOrderDO::getOutStatus, reqVO.getOutStatus())
+ .eqIfPresent(ErpSaleOrderDO::getProductionStatus, reqVO.getProductionStatus())
.likeIfPresent(ErpSaleOrderDO::getRemark, reqVO.getRemark())
.eqIfPresent(ErpSaleOrderDO::getCreator, reqVO.getCreator())
.orderByDesc(ErpSaleOrderDO::getId);
@@ -57,9 +62,10 @@
}
query.in(ErpSaleOrderDO::getCustomerId, permittedCustomerIds);
}
- if (reqVO.getProductId() != null) {
+ if (reqVO.getProductId() != null || reqVO.getNeedProduction() != null) {
query.leftJoin(ErpSaleOrderItemDO.class, ErpSaleOrderItemDO::getOrderId, ErpSaleOrderDO::getId)
.eq(reqVO.getProductId() != null, ErpSaleOrderItemDO::getProductId, reqVO.getProductId())
+ .eq(reqVO.getNeedProduction() != null, ErpSaleOrderItemDO::getNeedProduction, reqVO.getNeedProduction())
.groupBy(ErpSaleOrderDO::getId); // 閬垮厤 1 瀵瑰鏌ヨ锛屼骇鐢熺浉鍚岀殑 1
}
return selectJoinPage(reqVO, ErpSaleOrderDO.class, query);
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApi.java
new file mode 100644
index 0000000..05bfeba
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApi.java
@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.module.mes.api.productsales;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * MES 閿�鍞嚭搴撳崟琛� API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MesWmProductSalesLineApi {
+
+ /**
+ * 鏍规嵁閿�鍞鍗曟槑缁� ID 鍒楄〃鏌ヨ鍑哄簱鎵瑰彿
+ *
+ * @param saleOrderItemIds 閿�鍞鍗曟槑缁� ID 鍒楄〃
+ * @return Map<閿�鍞鍗曟槑缁咺D, 鎵瑰彿涓茶仈瀛楃涓�>
+ */
+ Map<Long, String> getBatchCodesBySaleOrderItemIds(Collection<Long> saleOrderItemIds);
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApi.java
new file mode 100644
index 0000000..5953c83
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApi.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.mes.api.returnsales;
+
+import cn.iocoder.yudao.module.mes.api.returnsales.dto.MesWmReturnSalesCreateReqDTO;
+
+/**
+ * MES 閿�鍞��璐у崟 API 鎺ュ彛
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public interface MesWmReturnSalesApi {
+
+ /**
+ * 鍒涘缓閿�鍞��璐у崟锛堢敱鍞悗妯″潡璋冪敤锛�
+ *
+ * @param reqDTO 鍒涘缓璇锋眰鍙傛暟
+ * @return 鍒涘缓鐨勯攢鍞��璐у崟缂栧彿
+ */
+ Long createReturnSales(MesWmReturnSalesCreateReqDTO reqDTO);
+
+}
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/dto/MesWmReturnSalesCreateReqDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/dto/MesWmReturnSalesCreateReqDTO.java
new file mode 100644
index 0000000..8d4e7e5
--- /dev/null
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/dto/MesWmReturnSalesCreateReqDTO.java
@@ -0,0 +1,44 @@
+package cn.iocoder.yudao.module.mes.api.returnsales.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+/**
+ * MES 閿�鍞��璐у崟鍒涘缓 Request DTO
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Schema(description = "RPC 鏈嶅姟 - MES 閿�鍞��璐у崟鍒涘缓 Request DTO")
+@Data
+public class MesWmReturnSalesCreateReqDTO {
+
+ @Schema(description = "閫�璐у崟缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "RS202603010001")
+ private String code;
+
+ @Schema(description = "閫�璐у崟鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鍞悗閫�璐�")
+ private String name;
+
+ @Schema(description = "閿�鍞鍗� ID", example = "1")
+ private Long saleOrderId;
+
+ @Schema(description = "閿�鍞鍗曠紪鍙�", example = "SO202603010001")
+ private String salesOrderCode;
+
+ @Schema(description = "瀹㈡埛 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ private Long clientId;
+
+ @Schema(description = "閫�璐ф棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED)
+ private LocalDate returnDate;
+
+ @Schema(description = "閫�璐у師鍥�", requiredMode = Schema.RequiredMode.REQUIRED, example = "璐ㄩ噺闂")
+ private String returnReason;
+
+ @Schema(description = "澶囨敞", example = "鍞悗閫�璐х敵璇峰叧鑱�")
+ private String remark;
+
+ @Schema(description = "鍞悗閫�璐х敵璇风紪鍙凤紙鍏宠仈 after_sale_return.id锛�", example = "1")
+ private Long afterSaleReturnId;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApiImpl.java
new file mode 100644
index 0000000..a59b512
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApiImpl.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.mes.api.productsales;
+
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * MES 閿�鍞嚭搴撳崟琛� API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+@Validated
+public class MesWmProductSalesLineApiImpl implements MesWmProductSalesLineApi {
+
+ @Resource
+ private MesWmProductSalesLineMapper productSalesLineMapper;
+
+ @Override
+ public Map<Long, String> getBatchCodesBySaleOrderItemIds(Collection<Long> saleOrderItemIds) {
+ return productSalesLineMapper.selectBatchCodesBySaleOrderItemIds(saleOrderItemIds);
+ }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApiImpl.java
new file mode 100644
index 0000000..67c278c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/MesWmReturnSalesApiImpl.java
@@ -0,0 +1,27 @@
+package cn.iocoder.yudao.module.mes.api.returnsales;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.api.returnsales.dto.MesWmReturnSalesCreateReqDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesSaveReqVO;
+import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+/**
+ * MES 閿�鍞��璐у崟 API 瀹炵幇绫�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@Service
+public class MesWmReturnSalesApiImpl implements MesWmReturnSalesApi {
+
+ @Resource
+ private MesWmReturnSalesService returnSalesService;
+
+ @Override
+ public Long createReturnSales(MesWmReturnSalesCreateReqDTO reqDTO) {
+ MesWmReturnSalesSaveReqVO createReqVO = BeanUtils.toBean(reqDTO, MesWmReturnSalesSaveReqVO.class);
+ return returnSalesService.createReturnSales(createReqVO);
+ }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/MesDvMachineryController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/MesDvMachineryController.java
index 932db26..864bf15 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/MesDvMachineryController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/MesDvMachineryController.java
@@ -52,6 +52,8 @@
private MesDvMachineryTypeService machineryTypeService;
@Resource
private MesMdWorkshopService workshopService;
+ @Resource
+ private cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService workstationService;
@PostMapping("/create")
@Operation(summary = "鍒涘缓璁惧")
@@ -151,17 +153,21 @@
if (CollUtil.isEmpty(list)) {
return Collections.emptyList();
}
- // 1. 鎵归噺鑾峰彇璁惧绫诲瀷鍜岃溅闂翠俊鎭�
+ // 1. 鎵归噺鑾峰彇璁惧绫诲瀷銆佽溅闂村拰宸ヤ綔绔欎俊鎭�
Map<Long, MesDvMachineryTypeDO> machineryTypeMap = machineryTypeService.getMachineryTypeMap(
convertSet(list, MesDvMachineryDO::getMachineryTypeId));
Map<Long, MesMdWorkshopDO> workshopMap = workshopService.getWorkshopMap(
convertSet(list, MesDvMachineryDO::getWorkshopId));
+ Map<Long, cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO> workstationMap =
+ workstationService.getWorkstationMap(convertSet(list, MesDvMachineryDO::getWorkstationId));
// 2. 鎷兼帴 VO
return BeanUtils.toBean(list, MesDvMachineryRespVO.class, vo -> {
MapUtils.findAndThen(machineryTypeMap, vo.getMachineryTypeId(),
machineryType -> vo.setMachineryTypeName(machineryType.getName()));
MapUtils.findAndThen(workshopMap, vo.getWorkshopId(),
workshop -> vo.setWorkshopName(workshop.getName()));
+ MapUtils.findAndThen(workstationMap, vo.getWorkstationId(),
+ workstation -> vo.setWorkstationName(workstation.getName()));
});
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryImportExcelVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryImportExcelVO.java
index 82b2364..fe5729d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryImportExcelVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryImportExcelVO.java
@@ -43,4 +43,13 @@
@ExcelProperty("澶囨敞")
private String remark;
+ @ExcelProperty("鐢熶骇鏁堢巼(姣忓皬鏃�)")
+ private java.math.BigDecimal productionEfficiency;
+
+ @ExcelProperty("浼犻�佸饱甯�")
+ private Integer conveyorBelt;
+
+ @ExcelProperty("宸ヤ綔绔欑紪鐮�")
+ private String workstationCode;
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryPageReqVO.java
index aefad44..2eea5ba 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryPageReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryPageReqVO.java
@@ -35,4 +35,7 @@
@Schema(description = "璁惧鐘舵��", example = "1")
private Integer status;
+ @Schema(description = "宸ヤ綔绔欑紪鍙�", example = "300")
+ private Long workstationId;
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
index 5d46e43..9834bbe 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachineryRespVO.java
@@ -66,6 +66,21 @@
@ExcelProperty("澶囨敞")
private String remark;
+ @Schema(description = "鐢熶骇鏁堢巼锛堟瘡灏忔椂锛�", example = "10.5")
+ @ExcelProperty("鐢熶骇鏁堢巼(姣忓皬鏃�)")
+ private java.math.BigDecimal productionEfficiency;
+
+ @Schema(description = "浼犻�佸饱甯︼紙2=鍙屽�嶆晥鐜囷級", example = "2")
+ @ExcelProperty("浼犻�佸饱甯�")
+ private Integer conveyorBelt;
+
+ @Schema(description = "宸ヤ綔绔欑紪鍙�", example = "300")
+ private Long workstationId;
+
+ @Schema(description = "宸ヤ綔绔欏悕绉�", example = "涓�鍙峰伐浣滅珯")
+ @ExcelProperty("宸ヤ綔绔�")
+ private String workstationName;
+
@Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("鍒涘缓鏃堕棿")
private LocalDateTime createTime;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
index e2aaad1..c9fecbc 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/machinery/vo/MesDvMachinerySaveReqVO.java
@@ -49,4 +49,13 @@
@Schema(description = "澶囨敞", example = "澶囨敞")
private String remark;
+ @Schema(description = "鐢熶骇鏁堢巼锛堟瘡灏忔椂锛�", example = "10.5")
+ private java.math.BigDecimal productionEfficiency;
+
+ @Schema(description = "浼犻�佸饱甯︼紙2=鍙屽�嶆晥鐜囷級", example = "2")
+ private Integer conveyorBelt;
+
+ @Schema(description = "宸ヤ綔绔欑紪鍙�", example = "300")
+ private Long workstationId;
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/documentaudit/vo/MesPdDocumentAuditPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/documentaudit/vo/MesPdDocumentAuditPageReqVO.java
index d41bf8f..fd1b1f3 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/documentaudit/vo/MesPdDocumentAuditPageReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/documentaudit/vo/MesPdDocumentAuditPageReqVO.java
@@ -21,4 +21,7 @@
@Schema(description = "瀹℃牳鐘舵��", example = "0")
private Integer auditStatus;
+ @Schema(description = "椤圭洰ID", example = "1")
+ private Long projectId;
+
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
index 19dd295..a0cca8c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
@@ -34,6 +34,7 @@
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
@@ -98,6 +99,20 @@
return success(true);
}
+ @GetMapping("/calculate-duration")
+ @Operation(summary = "璁$畻鐢熶骇鏃堕暱", description = "鏍规嵁宸ヤ綔绔欑粦瀹氳澶囩殑鐢熶骇鏁堢巼鍜屼紶閫佸饱甯︼紝鑷姩璁$畻鐢熶骇鏃堕暱锛堝皬鏃讹級")
+ @Parameters({
+ @Parameter(name = "workstationId", description = "宸ヤ綔绔欑紪鍙�", required = true),
+ @Parameter(name = "quantity", description = "鎺掍骇鏁伴噺", required = true)
+ })
+ @PreAuthorize("@ss.hasPermission('mes:pro-task:query')")
+ public CommonResult<java.util.Map<String, Object>> calculateDuration(
+ @RequestParam("workstationId") Long workstationId,
+ @RequestParam("quantity") BigDecimal quantity) {
+ Integer duration = taskService.calculateDuration(workstationId, quantity);
+ return success(java.util.Map.of("duration", duration));
+ }
+
@DeleteMapping("/delete")
@Operation(summary = "鍒犻櫎鐢熶骇浠诲姟")
@Parameter(name = "id", description = "缂栧彿", required = true)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesPageReqVO.java
index 17ef39a..3a2e459 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesPageReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesPageReqVO.java
@@ -6,9 +6,9 @@
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
/**
* 绠$悊鍚庡彴 - MES 閿�鍞��璐х浉鍏�
@@ -26,6 +26,9 @@
@Schema(description = "閫�璐у崟鍚嶇О", example = "閿�鍞��璐�")
private String name;
+ @Schema(description = "閿�鍞鍗� ID", example = "1")
+ private Long saleOrderId;
+
@Schema(description = "閿�鍞鍗曠紪鍙�", example = "SO202603010001")
private String salesOrderCode;
@@ -36,7 +39,7 @@
private Integer status;
@Schema(description = "閫�璐ф棩鏈�")
- @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
- private LocalDateTime[] returnDate;
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+ private LocalDate[] returnDate;
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesRespVO.java
index 14b8f86..8b8f429 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesRespVO.java
@@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
+import java.time.LocalDate;
import java.time.LocalDateTime;
/**
@@ -23,6 +24,9 @@
@Schema(description = "閫�璐у崟鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "閿�鍞��璐�")
private String name;
+ @Schema(description = "閿�鍞鍗� ID", example = "1")
+ private Long saleOrderId;
+
@Schema(description = "閿�鍞鍗曠紪鍙�", example = "SO202603010001")
private String salesOrderCode;
@@ -33,7 +37,7 @@
private String clientName;
@Schema(description = "閫�璐ф棩鏈�")
- private LocalDateTime returnDate;
+ private LocalDate returnDate;
@Schema(description = "閫�璐у師鍥�", example = "璐ㄩ噺闂")
private String returnReason;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesSaveReqVO.java
index 15caf1f..f2c0654 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesSaveReqVO.java
@@ -5,7 +5,7 @@
import jakarta.validation.constraints.NotNull;
import lombok.Data;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
/**
* 绠$悊鍚庡彴 - MES 閿�鍞��璐у崟鏂板/淇敼 Request VO
@@ -27,6 +27,9 @@
@NotBlank(message = "閫�璐у崟鍚嶇О涓嶈兘涓虹┖")
private String name;
+ @Schema(description = "閿�鍞鍗� ID", example = "1")
+ private Long saleOrderId;
+
@Schema(description = "閿�鍞鍗曠紪鍙�", example = "SO202603010001")
private String salesOrderCode;
@@ -36,7 +39,7 @@
@Schema(description = "閫�璐ф棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "閫�璐ф棩鏈熶笉鑳戒负绌�")
- private LocalDateTime returnDate;
+ private LocalDate returnDate;
@Schema(description = "閫�璐у師鍥�", requiredMode = Schema.RequiredMode.REQUIRED, example = "璐ㄩ噺闂")
@NotBlank(message = "閫�璐у師鍥犱笉鑳戒负绌�")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java
index e6f0263..f3936b0 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java
@@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
+import java.time.LocalDate;
import java.time.LocalDateTime;
@Schema(description = "绠$悊鍚庡彴 - MES 渚涘簲鍟嗛��璐у崟 Response VO")
@@ -34,7 +35,7 @@
private String vendorNickname;
@Schema(description = "閫�璐ф棩鏈�")
- private LocalDateTime returnDate;
+ private LocalDate returnDate;
@Schema(description = "閫�璐у師鍥�", example = "璐ㄩ噺涓嶅悎鏍�")
private String returnReason;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java
index 720348a..da089e2 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java
@@ -5,7 +5,7 @@
import jakarta.validation.constraints.NotNull;
import lombok.Data;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
@Schema(description = "绠$悊鍚庡彴 - MES 渚涘簲鍟嗛��璐у崟鏂板/淇敼 Request VO")
@Data
@@ -31,7 +31,7 @@
@Schema(description = "閫�璐ф棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "閫�璐ф棩鏈熶笉鑳戒负绌�")
- private LocalDateTime returnDate;
+ private LocalDate returnDate;
@Schema(description = "閫�璐у師鍥�", example = "璐ㄩ噺涓嶅悎鏍�")
private String returnReason;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
index bb65941..5caf847 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/machinery/MesDvMachineryDO.java
@@ -76,5 +76,19 @@
* 澶囨敞
*/
private String remark;
+ /**
+ * 鐢熶骇鏁堢巼锛堟瘡灏忔椂锛�
+ */
+ private java.math.BigDecimal productionEfficiency;
+ /**
+ * 浼犻�佸饱甯︼紙2=鍙屽�嶆晥鐜囷級
+ */
+ private Integer conveyorBelt;
+ /**
+ * 宸ヤ綔绔欑紪鍙�
+ *
+ * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO#getId()}
+ */
+ private Long workstationId;
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnsales/MesWmReturnSalesDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnsales/MesWmReturnSalesDO.java
index baa5daa..6faeffb 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnsales/MesWmReturnSalesDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnsales/MesWmReturnSalesDO.java
@@ -7,7 +7,7 @@
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
/**
@@ -39,6 +39,10 @@
*/
private String name;
/**
+ * 閿�鍞鍗� ID
+ */
+ private Long saleOrderId;
+ /**
* 閿�鍞鍗曠紪鍙�
*/
private String salesOrderCode;
@@ -51,7 +55,7 @@
/**
* 閫�璐ф棩鏈�
*/
- private LocalDateTime returnDate;
+ private LocalDate returnDate;
/**
* 閫�璐у師鍥�
*/
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java
index 9b8b4be..476fd5f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java
@@ -7,7 +7,7 @@
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
/**
@@ -49,7 +49,7 @@
/**
* 閫�璐ф棩鏈�
*/
- private LocalDateTime returnDate;
+ private LocalDate returnDate;
/**
* 閫�璐у師鍥�
*/
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/machinery/MesDvMachineryMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/machinery/MesDvMachineryMapper.java
index 5e39c3d..a6b2112 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/machinery/MesDvMachineryMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/machinery/MesDvMachineryMapper.java
@@ -24,6 +24,7 @@
.eqIfPresent(MesDvMachineryDO::getMachineryTypeId, reqVO.getMachineryTypeId())
.inIfPresent(MesDvMachineryDO::getMachineryTypeId, reqVO.getMachineryTypeIds())
.eqIfPresent(MesDvMachineryDO::getWorkshopId, reqVO.getWorkshopId())
+ .eqIfPresent(MesDvMachineryDO::getWorkstationId, reqVO.getWorkstationId())
.eqIfPresent(MesDvMachineryDO::getStatus, reqVO.getStatus())
.orderByDesc(MesDvMachineryDO::getId));
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/documentaudit/MesPdDocumentAuditMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/documentaudit/MesPdDocumentAuditMapper.java
index f876368..bb34e18 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/documentaudit/MesPdDocumentAuditMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pd/documentaudit/MesPdDocumentAuditMapper.java
@@ -21,6 +21,16 @@
.orderByDesc(MesPdDocumentAuditDO::getId));
}
+ default PageResult<MesPdDocumentAuditDO> selectPage(MesPdDocumentAuditPageReqVO reqVO,
+ Collection<Long> documentIds) {
+ return selectPage(reqVO, new LambdaQueryWrapperX<MesPdDocumentAuditDO>()
+ .eqIfPresent(MesPdDocumentAuditDO::getDocumentId, reqVO.getDocumentId())
+ .likeIfPresent(MesPdDocumentAuditDO::getAuditNo, reqVO.getAuditNo())
+ .eqIfPresent(MesPdDocumentAuditDO::getAuditStatus, reqVO.getAuditStatus())
+ .inIfPresent(MesPdDocumentAuditDO::getDocumentId, documentIds)
+ .orderByDesc(MesPdDocumentAuditDO::getId));
+ }
+
default List<MesPdDocumentAuditDO> selectListByDocumentId(Long documentId) {
return selectList(MesPdDocumentAuditDO::getDocumentId, documentId);
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesLineMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesLineMapper.java
index 9dcc9e4..6068a10 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesLineMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesLineMapper.java
@@ -1,11 +1,13 @@
package cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
+import java.util.Map;
/**
* MES 閿�鍞嚭搴撳崟琛� Mapper
@@ -31,6 +33,26 @@
.orderByDesc(MesWmProductSalesLineDO::getId));
}
+ /**
+ * 鏍规嵁閿�鍞鍗曟槑缁� ID 鍒楄〃鏌ヨ鍑哄簱琛岋紝鎸� saleOrderItemId 鍒嗙粍杩斿洖鎵瑰彿
+ */
+ default Map<Long, String> selectBatchCodesBySaleOrderItemIds(java.util.Collection<Long> saleOrderItemIds) {
+ if (saleOrderItemIds == null || saleOrderItemIds.isEmpty()) {
+ return java.util.Collections.emptyMap();
+ }
+ List<MesWmProductSalesLineDO> lines = selectList(new LambdaQueryWrapperX<MesWmProductSalesLineDO>()
+ .in(MesWmProductSalesLineDO::getSaleOrderItemId, saleOrderItemIds));
+ return lines.stream()
+ .filter(l -> l.getBatchCode() != null && !l.getBatchCode().isEmpty())
+ .collect(java.util.stream.Collectors.groupingBy(
+ MesWmProductSalesLineDO::getSaleOrderItemId,
+ java.util.LinkedHashMap::new,
+ java.util.stream.Collectors.collectingAndThen(
+ java.util.stream.Collectors.mapping(MesWmProductSalesLineDO::getBatchCode,
+ java.util.stream.Collectors.toSet()),
+ codes -> String.join("銆�", codes))));
+ }
+
default void updateQualityStatusByIds(List<Long> ids, Integer qualityStatus) {
update(new MesWmProductSalesLineDO().setQualityStatus(qualityStatus),
new LambdaUpdateWrapper<MesWmProductSalesLineDO>().in(MesWmProductSalesLineDO::getId, ids));
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index 6b6e280..eb2ae1a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -169,6 +169,7 @@
ErrorCode DV_MACHINERY_HAS_CHECK_RECORD = new ErrorCode(1_040_301_004, "璁惧宸插叧鑱旂偣妫�璁板綍锛屾棤娉曞垹闄�");
ErrorCode DV_MACHINERY_HAS_MAINTEN_RECORD = new ErrorCode(1_040_301_005, "璁惧宸插叧鑱斾繚鍏昏褰曪紝鏃犳硶鍒犻櫎");
ErrorCode DV_MACHINERY_HAS_REPAIR = new ErrorCode(1_040_301_006, "璁惧宸插叧鑱旂淮淇伐鍗曪紝鏃犳硶鍒犻櫎");
+ ErrorCode DV_MACHINERY_WORKSTATION_NOT_EXISTS = new ErrorCode(1_040_301_008, "宸ヤ綔绔欑紪鐮佷笉瀛樺湪");
// ========== MES 璁惧绠$悊-鐐规淇濆吇椤圭洰锛�1-040-304-000锛� ==========
ErrorCode DV_SUBJECT_NOT_EXISTS = new ErrorCode(1_040_304_000, "鐐规淇濆吇椤圭洰涓嶅瓨鍦�");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java
index 9e74cf6..2325b04 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/machinery/MesDvMachineryServiceImpl.java
@@ -54,6 +54,9 @@
@Lazy
private MesMdWorkshopService workshopService;
@Resource
+ @Lazy
+ private cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService workstationService;
+ @Resource
private MesWmBarcodeService barcodeService;
@Resource
@Lazy
@@ -201,7 +204,7 @@
throw exception(DV_MACHINERY_IMPORT_LIST_IS_EMPTY);
}
- // 2. 鎵归噺鍔犺浇璁惧绫诲瀷鍜岃溅闂达紝鏋勫缓缂栫爜鍒板疄浣撶殑鏄犲皠
+ // 2. 鎵归噺鍔犺浇璁惧绫诲瀷銆佽溅闂村拰宸ヤ綔绔欙紝鏋勫缓缂栫爜鍒板疄浣撶殑鏄犲皠
List<MesDvMachineryTypeDO> allTypes = machineryTypeService.getMachineryTypeList(
new cn.iocoder.yudao.module.mes.controller.admin.dv.machinery.vo.type.MesDvMachineryTypeListReqVO());
Map<String, MesDvMachineryTypeDO> typeCodeMap = allTypes.stream()
@@ -210,6 +213,13 @@
cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus());
Map<String, MesMdWorkshopDO> workshopCodeMap = allWorkshops.stream()
.collect(Collectors.toMap(MesMdWorkshopDO::getCode, w -> w, (a, b) -> a));
+ List<cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO> allWorkstations =
+ workstationService.getWorkstationListByStatus(
+ cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus());
+ Map<String, cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO> workstationCodeMap =
+ allWorkstations.stream().collect(Collectors.toMap(
+ cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO::getCode,
+ w -> w, (a, b) -> a));
// 3. 閬嶅巻锛岄�愪釜鍒涘缓 or 鏇存柊
MesDvMachineryImportRespVO respVO = MesDvMachineryImportRespVO.builder()
@@ -249,24 +259,38 @@
return;
}
- // 3.4 鍒ゆ柇锛氬垱寤� or 鏇存柊
+ // 3.4 鏍¢獙宸ヤ綔绔欑紪鐮侊紙鍙�夛級
+ Long workstationId = null;
+ if (StrUtil.isNotBlank(importItem.getWorkstationCode())) {
+ cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO workstation =
+ workstationCodeMap.get(importItem.getWorkstationCode());
+ if (workstation == null) {
+ respVO.getFailureCodes().put(key, "宸ヤ綔绔欑紪鐮乕" + importItem.getWorkstationCode() + "]涓嶅瓨鍦�");
+ return;
+ }
+ workstationId = workstation.getId();
+ }
+
+ // 3.5 鍒ゆ柇锛氬垱寤� or 鏇存柊
MesDvMachineryDO existMachinery = machineryMapper.selectByCode(importItem.getCode());
if (existMachinery == null) {
- // 3.4.1 鍒涘缓
+ // 3.5.1 鍒涘缓
MesDvMachineryDO machinery = BeanUtils.toBean(importItem, MesDvMachineryDO.class);
machinery.setMachineryTypeId(machineryType.getId());
machinery.setWorkshopId(workshop.getId());
+ machinery.setWorkstationId(workstationId);
machineryMapper.insert(machinery);
// 鑷姩鐢熸垚鏉$爜
barcodeService.autoGenerateBarcode(BarcodeBizTypeEnum.MACHINERY.getValue(),
machinery.getId(), machinery.getCode(), machinery.getName());
respVO.getCreateCodes().add(importItem.getCode());
} else if (updateSupport) {
- // 3.4.2 鏇存柊
+ // 3.5.2 鏇存柊
MesDvMachineryDO updateObj = BeanUtils.toBean(importItem, MesDvMachineryDO.class);
updateObj.setId(existMachinery.getId());
updateObj.setMachineryTypeId(machineryType.getId());
updateObj.setWorkshopId(workshop.getId());
+ updateObj.setWorkstationId(workstationId);
machineryMapper.updateById(updateObj);
respVO.getUpdateCodes().add(importItem.getCode());
} else {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/documentaudit/MesPdDocumentAuditServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/documentaudit/MesPdDocumentAuditServiceImpl.java
index 1119fc3..d9c1f43 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/documentaudit/MesPdDocumentAuditServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/documentaudit/MesPdDocumentAuditServiceImpl.java
@@ -4,7 +4,9 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pd.documentaudit.vo.MesPdDocumentAuditPageReqVO;
+import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.mes.controller.admin.pd.documentaudit.vo.MesPdDocumentAuditSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.documentaudit.MesPdDocumentAuditDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pd.documentaudit.MesPdDocumentAuditMapper;
import cn.iocoder.yudao.module.mes.service.pd.document.MesPdDocumentService;
@@ -14,7 +16,9 @@
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
+import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PD_DOCUMENT_AUDIT_NOT_EXISTS;
@@ -72,6 +76,17 @@
@Override
public PageResult<MesPdDocumentAuditDO> getDocumentAuditPage(MesPdDocumentAuditPageReqVO pageReqVO) {
+ // 鎸夐」鐩瓫閫夛細鍏堟煡鍑洪」鐩笅鎵�鏈夎祫鏂欙紝鍐嶆寜璧勬枡 ID 杩囨护瀹℃牳璁板綍
+ if (pageReqVO.getProjectId() != null) {
+ List<MesPdDocumentDO> documents = documentService.getDocumentListByProjectId(
+ pageReqVO.getProjectId());
+ if (CollUtil.isEmpty(documents)) {
+ return PageResult.empty();
+ }
+ List<Long> documentIds = documents.stream()
+ .map(MesPdDocumentDO::getId).collect(Collectors.toList());
+ return documentAuditMapper.selectPage(pageReqVO, documentIds);
+ }
return documentAuditMapper.selectPage(pageReqVO);
}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
index f3180d5..e47abc7 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskService.java
@@ -123,6 +123,15 @@
void cancelTaskByOrderId(Long workOrderId);
/**
+ * 鏍规嵁宸ヤ綔绔欑粦瀹氳澶囩殑鐢熶骇鏁堢巼鍜屼紶閫佸饱甯︼紝璁$畻鐢熶骇鏃堕暱锛堝崟浣嶏細灏忔椂锛�
+ *
+ * @param workstationId 宸ヤ綔绔欑紪鍙�
+ * @param quantity 鎺掍骇鏁伴噺
+ * @return 鐢熶骇鏃堕暱锛堝皬鏃讹級锛屾棤璁惧鍒欒繑鍥� 0
+ */
+ Integer calculateDuration(Long workstationId, BigDecimal quantity);
+
+ /**
* 绱姞浠诲姟鐨勫凡鐢熶骇銆佸悎鏍笺�佷笉鍚堟牸鏁伴噺
*
* @param id 浠诲姟缂栧彿
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
index d19426b..bf04d45 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -7,17 +7,21 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationMachineDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
+import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
+import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationMachineService;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
@@ -40,9 +44,11 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_ALREADY_FINISHED;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_HAS_FEEDBACK;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_EXISTS;
@@ -86,6 +92,11 @@
private MesProWorkOrderProcessService workOrderProcessService;
@Resource
private MesMdAutoCodeRecordService autoCodeRecordService;
+ @Resource
+ private MesMdWorkstationMachineService workstationMachineService;
+ @Resource
+ @Lazy
+ private MesDvMachineryService machineryService;
@Override
public Long createTask(MesProTaskSaveReqVO createReqVO) {
@@ -318,6 +329,33 @@
}
@Override
+ public Integer calculateDuration(Long workstationId, BigDecimal quantity) {
+ if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+ return 0;
+ }
+ // 1. 鏌ヨ宸ヤ綔绔欑粦瀹氱殑璁惧
+ List<MesMdWorkstationMachineDO> machines = workstationMachineService
+ .getWorkstationMachineListByWorkstationId(workstationId);
+ if (CollUtil.isEmpty(machines)) {
+ return 0;
+ }
+ // 2. 鑾峰彇璁惧璇︽儏锛岃绠楁�绘晥鐜�
+ Set<Long> machineryIds = convertSet(machines, MesMdWorkstationMachineDO::getMachineryId);
+ List<MesDvMachineryDO> machineryList = machineryService.getMachineryList(machineryIds);
+ BigDecimal totalEfficiency = BigDecimal.ZERO;
+ for (MesDvMachineryDO m : machineryList) {
+ BigDecimal efficiency = m.getProductionEfficiency() != null ? m.getProductionEfficiency() : BigDecimal.ZERO;
+ Integer belt = m.getConveyorBelt() != null && m.getConveyorBelt() > 0 ? m.getConveyorBelt() : 1;
+ totalEfficiency = totalEfficiency.add(efficiency.multiply(BigDecimal.valueOf(belt)));
+ }
+ if (totalEfficiency.compareTo(BigDecimal.ZERO) <= 0) {
+ return 0;
+ }
+ // 3. 鐢熶骇鏃堕暱锛堝皬鏃讹級 = ceil(鎺掍骇鏁伴噺 / 鎬绘晥鐜�)锛屾晥鐜囧崟浣嶄负姣忓皬鏃朵骇閲�
+ return quantity.divide(totalEfficiency, 0, java.math.RoundingMode.CEILING).intValue();
+ }
+
+ @Override
public MesProTaskAutoGeneratePreviewRespVO previewAutoGenerate(MesProTaskAutoGenerateReqVO reqVO) {
// 1. 鏍¢獙宸ュ崟宸茬‘璁�
MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java
index 9457f46..6b7fb1d 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java
@@ -3,6 +3,7 @@
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.*;
+import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidAwardDO;
import cn.iocoder.yudao.module.srm.service.tender.SrmBidAwardService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -42,7 +43,20 @@
@Operation(summary = "鏍规嵁鎷涙爣椤圭洰ID鑾峰彇瀹氭爣璇︽儏")
@PreAuthorize("@ss.hasPermission('srm:bid-award:query')")
public CommonResult<SrmBidAwardRespVO> getAwardByProject(@RequestParam("tenderProjectId") Long tenderProjectId) {
- return success(BeanUtils.toBean(bidAwardService.getAwardByProjectId(tenderProjectId), SrmBidAwardRespVO.class));
+ SrmBidAwardDO award = bidAwardService.getAwardByProjectId(tenderProjectId);
+ if (award == null) {
+ return success((SrmBidAwardRespVO) null);
+ }
+ return success(BeanUtils.toBean(award, SrmBidAwardRespVO.class));
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "鍒犻櫎瀹氭爣")
+ @Parameter(name = "id", description = "瀹氭爣ID", required = true)
+ @PreAuthorize("@ss.hasPermission('srm:bid-award:update')")
+ public CommonResult<Boolean> deleteAward(@RequestParam("id") Long id) {
+ bidAwardService.deleteAward(id);
+ return success(true);
}
@PutMapping("/approve")
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java
index 68e69a5..793f1bb 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java
@@ -40,6 +40,14 @@
return success(bidEvaluationService.createEvaluation(createReqVO));
}
+ @PutMapping("/update")
+ @Operation(summary = "鏇存柊璇勬爣璁板綍")
+ @PreAuthorize("@ss.hasPermission('srm:bid-evaluation:update')")
+ public CommonResult<Boolean> updateEvaluation(@Valid @RequestBody SrmBidEvaluationSaveReqVO updateReqVO) {
+ bidEvaluationService.updateEvaluation(updateReqVO);
+ return success(true);
+ }
+
@GetMapping("/list-by-project")
@Operation(summary = "鎸夋嫑鏍囬」鐩幏鍙栬瘎鏍囧垪琛�")
@PreAuthorize("@ss.hasPermission('srm:bid-evaluation:query')")
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java
index cab0f50..54e23a7 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java
@@ -111,6 +111,7 @@
if (material != null) {
vo.setTenderMaterialCode(material.getProductCode());
vo.setTenderMaterialName(material.getProductName());
+ vo.setMaterialName(material.getProductName());
vo.setTenderMaterialSpec(material.getProductSpec());
vo.setTenderMaterialUnit(material.getUnit());
}
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java
index b4b81ed..2e5d968 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java
@@ -31,6 +31,9 @@
@Schema(description = "鐗╂枡鍚嶇О")
private String tenderMaterialName;
+ @Schema(description = "鐗╂枡鍚嶇О锛堜笌鍓嶇 materialName 瀵瑰簲锛�")
+ private String materialName;
+
@Schema(description = "鐗╂枡瑙勬牸")
private String tenderMaterialSpec;
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java
index fd1cc12..766e20c 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java
@@ -21,4 +21,9 @@
.eq(SrmTenderBidDO::getSupplierId, supplierId));
}
+ default boolean existsByBidNo(String bidNo) {
+ return exists(new LambdaQueryWrapperX<SrmTenderBidDO>()
+ .eq(SrmTenderBidDO::getBidNo, bidNo));
+ }
+
}
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
index 72cef94..e685314 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
@@ -37,6 +37,7 @@
ErrorCode TENDER_STATUS_ALREADY_CLOSED = new ErrorCode(1_040_500_010, "宸插叧闂殑椤圭洰涓嶅厑璁告搷浣�");
ErrorCode BID_NOT_EXISTS = new ErrorCode(1_040_500_003, "鎶曟爣璁板綍涓嶅瓨鍦�");
ErrorCode BID_SUPPLIER_EXISTS = new ErrorCode(1_040_500_004, "渚涘簲鍟嗗凡鍙備笌鎶曟爣");
+ ErrorCode BID_NO_EXISTS = new ErrorCode(1_040_500_016, "鎶曟爣缂栧彿宸插瓨鍦�");
ErrorCode BID_PAST_END_TIME = new ErrorCode(1_040_500_005, "鎶曟爣宸叉埅姝�");
ErrorCode BID_SUPPLIER_NOT_APPROVED = new ErrorCode(1_040_500_011, "渚涘簲鍟嗘湭閫氳繃鍑嗗叆瀹℃壒锛屾棤娉曞弬涓庢姇鏍�");
ErrorCode BID_WITHDRAWN_CANNOT_QUOTE = new ErrorCode(1_040_500_012, "鎶曟爣宸叉挙鏍囷紝涓嶅厑璁告姤浠�");
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
index 42b8c4c..9e7b9be 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
@@ -17,6 +17,7 @@
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.hutool.core.collection.CollUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java
index e11e855..d27fd62 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java
@@ -25,6 +25,11 @@
SrmBidAwardDO getAwardByProjectId(Long tenderProjectId);
/**
+ * 鍒犻櫎瀹氭爣
+ */
+ void deleteAward(Long id);
+
+ /**
* 瀹℃壒瀹氭爣
*/
void approveAward(Long id);
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
index c27da39..962feb8 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
@@ -15,10 +15,12 @@
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
+import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
@@ -29,6 +31,7 @@
*/
@Service
@Validated
+@Slf4j
public class SrmBidAwardServiceImpl implements SrmBidAwardService {
@Resource
@@ -86,6 +89,13 @@
@Override
public SrmBidAwardDO getAwardByProjectId(Long tenderProjectId) {
return bidAwardMapper.selectByTenderProjectId(tenderProjectId);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deleteAward(Long id) {
+ SrmBidAwardDO award = validateAwardExists(id);
+ bidAwardMapper.deleteById(id);
}
@Override
@@ -169,15 +179,33 @@
ErpPurchaseOrderCreateReqDTO createReq = new ErpPurchaseOrderCreateReqDTO();
createReq.setSupplierId(srmSupplier.getId());
createReq.setOrderTime(LocalDateTime.now());
+ createReq.setDepositPrice(BigDecimal.ZERO);
createReq.setItems(orderItems);
createReq.setRemark("SRM 瀹氭爣鑷姩鐢熸垚, 瀹氭爣缂栧彿: " + award.getAwardNo());
Long purchaseOrderId = erpPurchaseOrderApi.createPurchaseOrder(createReq).getCheckedData();
+ // 鑾峰彇閲囪喘璁㈠崟鍙�
+ String purchaseOrderNo = null;
+ try {
+ purchaseOrderNo = erpPurchaseOrderApi.getPurchaseOrder(purchaseOrderId)
+ .getCheckedData().getNo();
+ } catch (Exception e) {
+ log.warn("[generatePurchaseOrder] 鑾峰彇閲囪喘璁㈠崟鍙峰け璐ワ紝purchaseOrderId={}", purchaseOrderId, e);
+ }
+
// 8. 鏇存柊瀹氭爣璁板綍
award.setPurchaseOrderId(purchaseOrderId);
+ award.setPurchaseOrderNo(purchaseOrderNo);
award.setAwardStatus(AwardStatusEnum.ORDER_GENERATED.getCode());
bidAwardMapper.updateById(award);
+
+ // 9. 鏇存柊鎷涙爣椤圭洰鐘舵��
+ SrmTenderProjectDO project = tenderProjectMapper.selectById(award.getTenderProjectId());
+ if (project != null) {
+ project.setTenderStatus(TenderStatusEnum.CLOSED.getCode());
+ tenderProjectMapper.updateById(project);
+ }
}
private SrmBidAwardDO validateAwardExists(Long id) {
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java
index 1614b1b..3ffd6a8 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java
@@ -20,6 +20,11 @@
Long createEvaluation(@Valid SrmBidEvaluationSaveReqVO createReqVO);
/**
+ * 鏇存柊璇勬爣璁板綍
+ */
+ void updateEvaluation(@Valid SrmBidEvaluationSaveReqVO updateReqVO);
+
+ /**
* 鑾峰彇璇勬爣鍒楄〃锛堟寜鎷涙爣椤圭洰锛�
*/
List<SrmBidEvaluationDO> getEvaluationList(Long tenderProjectId);
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java
index fa1f95e..c0f3b72 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java
@@ -44,6 +44,8 @@
@Transactional(rollbackFor = Exception.class)
public Long createEvaluation(SrmBidEvaluationSaveReqVO createReqVO) {
SrmBidEvaluationDO evaluation = BeanUtils.toBean(createReqVO, SrmBidEvaluationDO.class);
+ // 寮哄埗缃┖ id锛岄伩鍏嶅墠绔紶鍏ュ凡鏈� id 瀵艰嚧涓婚敭鍐茬獊锛岀敱鏁版嵁搴撳簭鍒楄嚜鍔ㄧ敓鎴�
+ evaluation.setId(null);
// 璁$畻缁煎悎璇勫垎: 浠锋牸*0.4 + 鎶�鏈�*0.3 + 浜や粯*0.2 + 鏈嶅姟*0.1
BigDecimal price = nvl(evaluation.getPriceScore());
BigDecimal tech = nvl(evaluation.getTechScore());
@@ -67,6 +69,28 @@
}
@Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updateEvaluation(SrmBidEvaluationSaveReqVO updateReqVO) {
+ SrmBidEvaluationDO existing = bidEvaluationMapper.selectById(updateReqVO.getId());
+ if (existing == null) {
+ throw ServiceExceptionUtil.exception(ErrorCodeConstants.EVALUATION_NOT_EXISTS_TENDER);
+ }
+ SrmBidEvaluationDO evaluation = BeanUtils.toBean(updateReqVO, SrmBidEvaluationDO.class);
+ BigDecimal price = nvl(evaluation.getPriceScore());
+ BigDecimal tech = nvl(evaluation.getTechScore());
+ BigDecimal delivery = nvl(evaluation.getDeliveryScore());
+ BigDecimal service = nvl(evaluation.getServiceScore());
+ BigDecimal composite = price.multiply(new BigDecimal("0.4"))
+ .add(tech.multiply(new BigDecimal("0.3")))
+ .add(delivery.multiply(new BigDecimal("0.2")))
+ .add(service.multiply(new BigDecimal("0.1")))
+ .setScale(2, RoundingMode.HALF_UP);
+ evaluation.setCompositeScore(composite);
+ evaluation.setEvaluationTime(LocalDateTime.now());
+ bidEvaluationMapper.updateById(evaluation);
+ }
+
+ @Override
public List<SrmBidEvaluationDO> getEvaluationList(Long tenderProjectId) {
return bidEvaluationMapper.selectListByTenderProjectId(tenderProjectId);
}
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
index 6931e61..91e6e74 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
@@ -51,6 +51,10 @@
if (!supplierApplyMapper.existsApprovedBySupplierId(createReqVO.getSupplierId())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_SUPPLIER_NOT_APPROVED);
}
+ // 鏍¢獙鎶曟爣缂栧彿鏄惁閲嶅
+ if (tenderBidMapper.existsByBidNo(createReqVO.getBidNo())) {
+ throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_NO_EXISTS);
+ }
// 鏍¢獙渚涘簲鍟嗘槸鍚﹀凡鍙備笌鎶曟爣
SrmTenderBidDO existing = tenderBidMapper.selectByTenderAndSupplier(createReqVO.getTenderProjectId(), createReqVO.getSupplierId());
if (existing != null) {
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
index 0048d8f..7de0819 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
@@ -11,6 +11,7 @@
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidAwardMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderMaterialMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper;
+import cn.iocoder.yudao.module.srm.enums.AwardStatusEnum;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
@@ -64,7 +65,15 @@
@Override
public SrmTenderProjectDO getTenderProject(Long id) {
- return tenderProjectMapper.selectById(id);
+ SrmTenderProjectDO project = tenderProjectMapper.selectById(id);
+ if (project != null) {
+ SrmBidAwardDO award = bidAwardMapper.selectByTenderProjectId(id);
+ if (award != null) {
+ project.setAwardId(award.getId());
+ project.setPurchaseOrderNo(award.getPurchaseOrderNo());
+ }
+ }
+ return project;
}
@Override
@@ -82,6 +91,10 @@
if (award != null) {
project.setAwardId(award.getId());
project.setPurchaseOrderNo(award.getPurchaseOrderNo());
+ // 闃插尽锛氬鏋滃畾鏍囧凡鐢熸垚璁㈠崟浣嗛」鐩姸鎬佹湭鏇存柊锛屽悓姝ョ姸鎬�
+ if (AwardStatusEnum.ORDER_GENERATED.getCode().equals(award.getAwardStatus())) {
+ project.setTenderStatus(TenderStatusEnum.CLOSED.getCode());
+ }
}
});
}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
index 1a7e647..63a5981 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -53,6 +53,10 @@
HRM_RESIGNATION_APPLICATION("hrm_resignation_application", "HRM 绂昏亴鐢宠"),
HRM_ATTENDANCE_EXCEPTION("hrm_attendance_exception", "HRM 鑰冨嫟寮傚父"),
+ // ===== 鍞悗 =====
+ AFTER_SALE_TICKET("aftersales_ticket", "鍞悗宸ュ崟"),
+ AFTER_SALE_RETURN("aftersales_return", "鍞悗閫�璐х敵璇�"),
+
// ===== MES =====
MES_PD_DOCUMENT("mes_pd_document", "MES 璁捐璧勬枡");
diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml
index 6df01b4..d275fc8 100644
--- a/yudao-server/pom.xml
+++ b/yudao-server/pom.xml
@@ -150,6 +150,13 @@
<version>${revision}</version>
</dependency>
+ <!-- 鍞悗宸ュ崟绠$悊妯″潡 -->
+ <dependency>
+ <groupId>cn.iocoder.boot</groupId>
+ <artifactId>yudao-module-aftersales</artifactId>
+ <version>${revision}</version>
+ </dependency>
+
<!-- spring boot 閰嶇疆鎵�闇�渚濊禆 -->
<dependency>
<groupId>org.springframework.boot</groupId>
--
Gitblit v1.9.3