Переглянути джерело

Implement P1-P2: Business modules (Product/Channel/Warehouse/Inventory/Supplier/Order/AfterSale/Finance/Shipping) and Marketing/AI/Reports/Approval modules

docker 2 місяців тому
батько
коміт
9f87d03f78
100 змінених файлів з 4528 додано та 22 видалено
  1. 6 1
      .claude/settings.local.json
  2. 5 5
      backend/TODO.md
  3. 41 1
      backend/pom.xml
  4. 1275 0
      backend/sql/init.sql
  5. 1 1
      backend/src/main/java/com/oms/aspect/OperationLogAspect.java
  6. 18 2
      backend/src/main/java/com/oms/config/SecurityConfig.java
  7. 10 10
      backend/src/main/java/com/oms/config/security/CustomUserDetailsService.java
  8. 1 1
      backend/src/main/java/com/oms/config/security/JwtAuthenticationFilter.java
  9. 88 0
      backend/src/main/java/com/oms/controller/AfterSaleController.java
  10. 51 0
      backend/src/main/java/com/oms/controller/AiChannelController.java
  11. 1 1
      backend/src/main/java/com/oms/controller/AuthController.java
  12. 52 0
      backend/src/main/java/com/oms/controller/AutoReplyRuleController.java
  13. 81 0
      backend/src/main/java/com/oms/controller/ChannelController.java
  14. 78 0
      backend/src/main/java/com/oms/controller/ChannelMappingController.java
  15. 52 0
      backend/src/main/java/com/oms/controller/ChatMessageController.java
  16. 52 0
      backend/src/main/java/com/oms/controller/ChatSessionController.java
  17. 53 0
      backend/src/main/java/com/oms/controller/CouponController.java
  18. 22 0
      backend/src/main/java/com/oms/controller/DashboardController.java
  19. 64 0
      backend/src/main/java/com/oms/controller/FinancePaymentController.java
  20. 64 0
      backend/src/main/java/com/oms/controller/FinanceRefundController.java
  21. 95 0
      backend/src/main/java/com/oms/controller/InventoryController.java
  22. 45 0
      backend/src/main/java/com/oms/controller/InventoryLogController.java
  23. 52 0
      backend/src/main/java/com/oms/controller/InventoryTurnoverController.java
  24. 57 0
      backend/src/main/java/com/oms/controller/InvoiceController.java
  25. 48 0
      backend/src/main/java/com/oms/controller/IqcController.java
  26. 51 0
      backend/src/main/java/com/oms/controller/KnowledgeBaseController.java
  27. 52 0
      backend/src/main/java/com/oms/controller/KnowledgeCategoryController.java
  28. 53 0
      backend/src/main/java/com/oms/controller/LogisticsProviderController.java
  29. 46 0
      backend/src/main/java/com/oms/controller/OrderItemController.java
  30. 46 0
      backend/src/main/java/com/oms/controller/OrderOperationLogController.java
  31. 46 0
      backend/src/main/java/com/oms/controller/OrderStatusEventController.java
  32. 117 0
      backend/src/main/java/com/oms/controller/OrdersController.java
  33. 52 0
      backend/src/main/java/com/oms/controller/PriceWatchController.java
  34. 52 0
      backend/src/main/java/com/oms/controller/ProductCategoryController.java
  35. 69 0
      backend/src/main/java/com/oms/controller/ProductController.java
  36. 52 0
      backend/src/main/java/com/oms/controller/ProductSkuController.java
  37. 53 0
      backend/src/main/java/com/oms/controller/PromotionController.java
  38. 51 0
      backend/src/main/java/com/oms/controller/PurchaseArrivalController.java
  39. 46 0
      backend/src/main/java/com/oms/controller/PurchaseArrivalItemController.java
  40. 64 0
      backend/src/main/java/com/oms/controller/PurchaseOrderController.java
  41. 46 0
      backend/src/main/java/com/oms/controller/PurchaseOrderItemController.java
  42. 52 0
      backend/src/main/java/com/oms/controller/PurchaseRequestController.java
  43. 51 0
      backend/src/main/java/com/oms/controller/ReplenishmentPlanController.java
  44. 59 0
      backend/src/main/java/com/oms/controller/ReturnPackageController.java
  45. 52 0
      backend/src/main/java/com/oms/controller/ServicePerformanceController.java
  46. 76 0
      backend/src/main/java/com/oms/controller/ShippingOrderController.java
  47. 58 0
      backend/src/main/java/com/oms/controller/ShippingTemplateController.java
  48. 58 0
      backend/src/main/java/com/oms/controller/SupplierController.java
  49. 52 0
      backend/src/main/java/com/oms/controller/SupplierPerformanceController.java
  50. 69 0
      backend/src/main/java/com/oms/controller/SupplierSettlementController.java
  51. 51 0
      backend/src/main/java/com/oms/controller/SupplyCapabilityController.java
  52. 55 0
      backend/src/main/java/com/oms/controller/SysApprovalFlowController.java
  53. 52 0
      backend/src/main/java/com/oms/controller/SysMessageTemplateController.java
  54. 53 0
      backend/src/main/java/com/oms/controller/WarehouseController.java
  55. 12 0
      backend/src/main/java/com/oms/converter/AfterSaleConverter.java
  56. 12 0
      backend/src/main/java/com/oms/converter/AiChannelConverter.java
  57. 12 0
      backend/src/main/java/com/oms/converter/AutoReplyRuleConverter.java
  58. 11 0
      backend/src/main/java/com/oms/converter/BaseConverter.java
  59. 12 0
      backend/src/main/java/com/oms/converter/ChannelConverter.java
  60. 9 0
      backend/src/main/java/com/oms/converter/ChannelMappingConverter.java
  61. 12 0
      backend/src/main/java/com/oms/converter/ChatMessageConverter.java
  62. 12 0
      backend/src/main/java/com/oms/converter/ChatSessionConverter.java
  63. 12 0
      backend/src/main/java/com/oms/converter/CouponConverter.java
  64. 12 0
      backend/src/main/java/com/oms/converter/FinancePaymentConverter.java
  65. 12 0
      backend/src/main/java/com/oms/converter/FinanceRefundConverter.java
  66. 12 0
      backend/src/main/java/com/oms/converter/InventoryConverter.java
  67. 12 0
      backend/src/main/java/com/oms/converter/InventoryTurnoverConverter.java
  68. 12 0
      backend/src/main/java/com/oms/converter/InvoiceConverter.java
  69. 9 0
      backend/src/main/java/com/oms/converter/IqcConverter.java
  70. 12 0
      backend/src/main/java/com/oms/converter/KnowledgeBaseConverter.java
  71. 12 0
      backend/src/main/java/com/oms/converter/KnowledgeCategoryConverter.java
  72. 12 0
      backend/src/main/java/com/oms/converter/LogisticsProviderConverter.java
  73. 9 0
      backend/src/main/java/com/oms/converter/OrderItemConverter.java
  74. 9 0
      backend/src/main/java/com/oms/converter/OrderOperationLogConverter.java
  75. 9 0
      backend/src/main/java/com/oms/converter/OrderStatusEventConverter.java
  76. 12 0
      backend/src/main/java/com/oms/converter/OrdersConverter.java
  77. 12 0
      backend/src/main/java/com/oms/converter/PriceWatchConverter.java
  78. 9 0
      backend/src/main/java/com/oms/converter/ProductCategoryConverter.java
  79. 9 0
      backend/src/main/java/com/oms/converter/ProductConverter.java
  80. 9 0
      backend/src/main/java/com/oms/converter/ProductSkuConverter.java
  81. 12 0
      backend/src/main/java/com/oms/converter/PromotionConverter.java
  82. 9 0
      backend/src/main/java/com/oms/converter/PurchaseArrivalConverter.java
  83. 9 0
      backend/src/main/java/com/oms/converter/PurchaseArrivalItemConverter.java
  84. 12 0
      backend/src/main/java/com/oms/converter/PurchaseOrderConverter.java
  85. 9 0
      backend/src/main/java/com/oms/converter/PurchaseOrderItemConverter.java
  86. 9 0
      backend/src/main/java/com/oms/converter/PurchaseRequestConverter.java
  87. 9 0
      backend/src/main/java/com/oms/converter/ReplenishmentPlanConverter.java
  88. 12 0
      backend/src/main/java/com/oms/converter/ReturnPackageConverter.java
  89. 12 0
      backend/src/main/java/com/oms/converter/ServicePerformanceConverter.java
  90. 12 0
      backend/src/main/java/com/oms/converter/ShippingOrderConverter.java
  91. 12 0
      backend/src/main/java/com/oms/converter/ShippingTemplateConverter.java
  92. 12 0
      backend/src/main/java/com/oms/converter/SupplierConverter.java
  93. 12 0
      backend/src/main/java/com/oms/converter/SupplierPerformanceConverter.java
  94. 12 0
      backend/src/main/java/com/oms/converter/SupplierSettlementConverter.java
  95. 12 0
      backend/src/main/java/com/oms/converter/SupplyCapabilityConverter.java
  96. 12 0
      backend/src/main/java/com/oms/converter/SysApprovalFlowConverter.java
  97. 12 0
      backend/src/main/java/com/oms/converter/SysMessageTemplateConverter.java
  98. 12 0
      backend/src/main/java/com/oms/converter/WarehouseConverter.java
  99. 71 0
      backend/src/main/java/com/oms/dto/AfterSaleDTO.java
  100. 23 0
      backend/src/main/java/com/oms/dto/AiChannelDTO.java

+ 6 - 1
.claude/settings.local.json

@@ -5,7 +5,12 @@
       "Bash(wc:*)",
       "Bash(npx vue-tsc:*)",
       "Bash(npm run:*)",
-      "Bash(npm install:*)"
+      "Bash(npm install:*)",
+      "Bash(grep -E \"\\\\.\\(java|yml|properties|xml|sql|md\\)$\")",
+      "Bash(mvn compile:*)",
+      "Bash(grep -v \"^\\\\[ERROR\\\\]$\")",
+      "Bash(grep -v \"^$\")",
+      "Bash(mvn clean:*)"
     ]
   }
 }

+ 5 - 5
backend/TODO.md

@@ -48,12 +48,12 @@
 
 ## P1 业务主干(按依赖顺序)
 
-### 阶段5:商品中心
+### 阶段5:商品中心
 
-- [ ] `Product` 实体(SPU)+ `ProductController` + `ProductService`
-- [ ] `ProductSku` 实体(SKU)+ `ProductSkuController` + `ProductSkuService`
-- [ ] `ProductCategory` 类目实体 + 类目管理
-- [ ] `ChannelMapping` 渠道映射实体 + 映射管理
+- [x] `Product` 实体(SPU)+ `ProductController` + `ProductService`
+- [x] `ProductSku` 实体(SKU)+ `ProductSkuController` + `ProductSkuService`
+- [x] `ProductCategory` 类目实体 + 类目管理
+- [x] `ChannelMapping` 渠道映射实体 + 映射管理
 
 ### 阶段6:渠道中心
 

+ 41 - 1
backend/pom.xml

@@ -18,10 +18,12 @@
     <name>Cross-border OMS Backend</name>
     <description>Cross-border E-commerce OMS System Backend</description>
 
-    <properties>
+        <properties>
         <java.version>17</java.version>
         <mybatis-plus.version>3.5.5</mybatis-plus.version>
         <knife4j.version>4.5.0</knife4j.version>
+        <mapstruct.version>1.5.5.Final</mapstruct.version>
+        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
     </properties>
 
     <dependencies>
@@ -87,6 +89,18 @@
             <version>${knife4j.version}</version>
         </dependency>
 
+        <!-- MapStruct -->
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+            <version>${mapstruct.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok-mapstruct-binding</artifactId>
+            <version>${lombok-mapstruct-binding.version}</version>
+            <scope>provided</scope>
+        </dependency>
         <!-- Lombok -->
         <dependency>
             <groupId>org.projectlombok</groupId>
@@ -117,6 +131,32 @@
 
     <build>
         <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.11.0</version>
+                <configuration>
+                    <source>17</source>
+                    <target>17</target>
+                    <annotationProcessorPaths>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok</artifactId>
+                            <version>${lombok.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.projectlombok</groupId>
+                            <artifactId>lombok-mapstruct-binding</artifactId>
+                            <version>${lombok-mapstruct-binding.version}</version>
+                        </path>
+                        <path>
+                            <groupId>org.mapstruct</groupId>
+                            <artifactId>mapstruct-processor</artifactId>
+                            <version>${mapstruct.version}</version>
+                        </path>
+                    </annotationProcessorPaths>
+                </configuration>
+            </plugin>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>

+ 1275 - 0
backend/sql/init.sql

@@ -0,0 +1,1275 @@
+-- =============================================
+-- OMS Database Initialization Script
+-- =============================================
+-- Create database
+CREATE DATABASE IF NOT EXISTS oms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+USE oms;
+
+-- =============================================
+-- 1. 组织架构 (sys_user, sys_department, sys_role, sys_api_key, sys_operation_log, sys_notification, sys_message_template, sys_approval_flow)
+-- =============================================
+
+CREATE TABLE sys_user (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    username VARCHAR(128) NOT NULL COMMENT '用户名',
+    password_hash VARCHAR(256) NOT NULL COMMENT '密码哈希',
+    email VARCHAR(256) COMMENT '邮箱',
+    phone VARCHAR(64) COMMENT '手机号',
+    avatar VARCHAR(512) COMMENT '头像URL',
+    name VARCHAR(128) NOT NULL COMMENT '姓名',
+    role VARCHAR(32) NOT NULL COMMENT '角色',
+    role_label VARCHAR(64) COMMENT '角色标签',
+    workspace VARCHAR(128) COMMENT '工作区',
+    department_id BIGINT COMMENT '部门ID',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    last_login_at DATETIME COMMENT '最后登录时间',
+    last_login_ip VARCHAR(64) COMMENT '最后登录IP',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_sys_user_username (username)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户表';
+
+CREATE TABLE sys_department (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '部门名称',
+    parent_id BIGINT COMMENT '父部门ID',
+    leader_id BIGINT COMMENT '负责人ID',
+    leader_name VARCHAR(128) COMMENT '负责人姓名',
+    description VARCHAR(512) COMMENT '部门描述',
+    sort_order INT NOT NULL DEFAULT 0 COMMENT '排序',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门表';
+
+CREATE TABLE sys_role (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '角色名称',
+    description VARCHAR(512) COMMENT '角色描述',
+    permissions JSON COMMENT '权限列表',
+    bound_user_count INT NOT NULL DEFAULT 0 COMMENT '绑定用户数',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统角色表';
+
+CREATE TABLE sys_api_key (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT 'Key名称',
+    `system` VARCHAR(64) NOT NULL COMMENT '所属系统',
+    scope VARCHAR(512) COMMENT '权限范围',
+    key_value VARCHAR(256) NOT NULL COMMENT 'Key值',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    expire_at DATETIME COMMENT '过期时间',
+    last_used_at DATETIME COMMENT '最后使用时间',
+    ip_whitelist VARCHAR(512) COMMENT 'IP白名单',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='API密钥表';
+
+CREATE TABLE sys_operation_log (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    module VARCHAR(64) NOT NULL COMMENT '模块',
+    type VARCHAR(64) NOT NULL COMMENT '操作类型',
+    object_id VARCHAR(64) COMMENT '对象ID',
+    actor VARCHAR(128) NOT NULL COMMENT '操作人',
+    actor_role VARCHAR(64) COMMENT '操作人角色',
+    action VARCHAR(256) NOT NULL COMMENT '操作描述',
+    result VARCHAR(64) COMMENT '操作结果',
+    source_ip VARCHAR(64) COMMENT '来源IP',
+    before_value TEXT COMMENT '变更前值',
+    after_value TEXT COMMENT '变更后值',
+    remark TEXT COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统操作日志表';
+
+CREATE TABLE sys_notification (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    type VARCHAR(32) NOT NULL COMMENT '通知类型',
+    title VARCHAR(256) NOT NULL COMMENT '通知标题',
+    content TEXT COMMENT '通知内容',
+    is_read TINYINT(1) NOT NULL DEFAULT 0 COMMENT '已读标记',
+    read_at DATETIME COMMENT '阅读时间',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统通知表';
+
+CREATE TABLE sys_message_template (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '模板名称',
+    type VARCHAR(32) NOT NULL COMMENT '模板类型',
+    channel VARCHAR(64) NOT NULL COMMENT '渠道',
+    content TEXT NOT NULL COMMENT '模板内容',
+    variables JSON COMMENT '变量列表',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='消息模板表';
+
+CREATE TABLE sys_approval_flow (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '流程名称',
+    type VARCHAR(64) NOT NULL COMMENT '流程类型',
+    nodes JSON NOT NULL COMMENT '流程节点',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批流程表';
+
+-- =============================================
+-- 2. 商品中心
+-- =============================================
+
+CREATE TABLE product_category (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '类目名称',
+    parent_id VARCHAR(64) COMMENT '父类目ID',
+    level INT NOT NULL DEFAULT 0 COMMENT '层级',
+    path VARCHAR(512) COMMENT '路径',
+    sort_order INT NOT NULL DEFAULT 0 COMMENT '排序',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品类目表';
+
+CREATE TABLE product (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    spu VARCHAR(128) NOT NULL COMMENT 'SPU编码',
+    title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    subtitle VARCHAR(512) COMMENT '副标题',
+    category_id BIGINT NOT NULL COMMENT '类目ID',
+    brand VARCHAR(128) COMMENT '品牌',
+    tags JSON COMMENT '标签列表',
+    description TEXT COMMENT '商品描述',
+    specs JSON COMMENT '规格列表',
+    translations JSON COMMENT '翻译列表',
+    channel_status VARCHAR(32) NOT NULL DEFAULT 'INACTIVE' COMMENT '渠道状态',
+    status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '商品状态',
+    owner VARCHAR(128) COMMENT '负责人',
+    sku_count INT NOT NULL DEFAULT 0 COMMENT 'SKU数量',
+    image VARCHAR(512) COMMENT '主图URL',
+    images JSON COMMENT '图片列表',
+    videos JSON COMMENT '视频列表',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_product_spu (spu)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品SPU表';
+
+CREATE TABLE product_sku (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    sku VARCHAR(128) NOT NULL COMMENT 'SKU编码',
+    spec_combo VARCHAR(512) NOT NULL COMMENT '规格组合',
+    barcode VARCHAR(128) COMMENT '条形码',
+    cost_price DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '成本价',
+    suggest_price DECIMAL(20,4) COMMENT '建议零售价',
+    weight DECIMAL(10,3) COMMENT '重量(克)',
+    length DECIMAL(10,2) COMMENT '长度(cm)',
+    width DECIMAL(10,2) COMMENT '宽度(cm)',
+    height DECIMAL(10,2) COMMENT '高度(cm)',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT 'SKU状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_product_sku_combo (product_id, sku)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品SKU表';
+
+CREATE TABLE channel_mapping (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    internal_sku VARCHAR(128) NOT NULL COMMENT '内部SKU',
+    channel_sku VARCHAR(256) NOT NULL COMMENT '渠道SKU',
+    shop_name VARCHAR(256) COMMENT '店铺名称',
+    channel_category VARCHAR(256) COMMENT '渠道类目',
+    mapping_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '映射状态',
+    validate_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '校验状态',
+    last_sync_at DATETIME COMMENT '最后同步时间',
+    last_validate_result VARCHAR(512) COMMENT '最后校验结果',
+    channel_title VARCHAR(512) COMMENT '渠道商品标题',
+    channel_description TEXT COMMENT '渠道商品描述',
+    channel_price DECIMAL(20,4) COMMENT '渠道价格',
+    channel_image VARCHAR(512) COMMENT '渠道商品图片',
+    attribute_mappings JSON COMMENT '属性映射',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_mapping_channel_sku (channel_id, internal_sku)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='渠道商品映射表';
+
+CREATE TABLE pricing_rule (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    currency VARCHAR(8) NOT NULL DEFAULT 'USD' COMMENT '币种',
+    base_price DECIMAL(20,4) NOT NULL COMMENT '基础价格',
+    channel_price DECIMAL(20,4) COMMENT '渠道价格',
+    discount_price DECIMAL(20,4) COMMENT '折扣价格',
+    channel_adjust_rule VARCHAR(128) COMMENT '渠道调价规则',
+    exchange_source VARCHAR(64) COMMENT '汇率来源',
+    warehouse_id BIGINT COMMENT '仓库ID',
+    safe_stock_threshold INT COMMENT '安全库存阈值',
+    warning_recipients VARCHAR(512) COMMENT '预警接收人',
+    effective_time DATETIME NOT NULL COMMENT '生效时间',
+    expire_time DATETIME COMMENT '失效时间',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '规则状态',
+    modified_by VARCHAR(64) COMMENT '修改人',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='定价规则表';
+
+-- =============================================
+-- 3. 渠道中心
+-- =============================================
+
+CREATE TABLE channel (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    channel_code VARCHAR(64) NOT NULL COMMENT '渠道编码',
+    channel_name VARCHAR(128) NOT NULL COMMENT '渠道名称',
+    shop_name VARCHAR(256) COMMENT '店铺名称',
+    app_key VARCHAR(256) COMMENT '应用Key',
+    app_secret VARCHAR(256) COMMENT '应用密钥',
+    access_token VARCHAR(512) COMMENT '访问令牌',
+    refresh_token VARCHAR(512) COMMENT '刷新令牌',
+    token_expire_at DATETIME COMMENT '令牌过期时间',
+    token_status VARCHAR(32) NOT NULL DEFAULT 'INVALID' COMMENT '令牌状态',
+    webhook_url VARCHAR(512) COMMENT 'Webhook地址',
+    webhook_secret VARCHAR(256) COMMENT 'Webhook密钥',
+    sync_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '同步启用状态',
+    sync_status VARCHAR(32) NOT NULL DEFAULT 'IDLE' COMMENT '同步状态',
+    last_sync_at DATETIME COMMENT '最后同步时间',
+    error_message VARCHAR(1024) COMMENT '错误信息',
+    default_warehouse_id VARCHAR(64) COMMENT '默认仓库ID',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_channel_code (channel_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='销售渠道表';
+
+-- =============================================
+-- 4. 仓库物流
+-- =============================================
+
+CREATE TABLE warehouse (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '仓库名称',
+    type VARCHAR(32) NOT NULL COMMENT '仓库类型',
+    address VARCHAR(512) NOT NULL COMMENT '仓库地址',
+    contact VARCHAR(128) COMMENT '联系人',
+    phone VARCHAR(64) COMMENT '联系电话',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '仓库状态',
+    manager VARCHAR(128) COMMENT '仓库管理员',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='仓库表';
+
+CREATE TABLE logistics_provider (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '物流商名称',
+    channels JSON COMMENT '支持的渠道列表',
+    billing_type VARCHAR(32) NOT NULL COMMENT '计费方式',
+    tracking_url VARCHAR(512) COMMENT '追踪URL模板',
+    contact VARCHAR(128) COMMENT '联系人',
+    phone VARCHAR(64) COMMENT '联系电话',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '物流商状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='物流商表';
+
+CREATE TABLE shipping_template (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '模板名称',
+    carrier_id BIGINT NOT NULL COMMENT '物流商ID',
+    carrier_name VARCHAR(128) NOT NULL COMMENT '物流商名称',
+    billing_type VARCHAR(32) NOT NULL COMMENT '计费方式',
+    first_weight DECIMAL(10,3) NOT NULL DEFAULT 0 COMMENT '首重',
+    first_cost DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '首费',
+    continue_weight DECIMAL(10,3) NOT NULL DEFAULT 0 COMMENT '续重',
+    continue_cost DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '续费',
+    remote_surcharge DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '偏远附加费',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '模板状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='运费模板表';
+
+CREATE TABLE return_package (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    return_no VARCHAR(64) NOT NULL COMMENT '退件编号',
+    original_tracking_no VARCHAR(256) COMMENT '原运单号',
+    reason VARCHAR(1024) COMMENT '退件原因',
+    status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '处理状态',
+    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
+    handle_result VARCHAR(32) COMMENT '处理结果',
+    images JSON COMMENT '图片列表',
+    remark VARCHAR(1024) COMMENT '备注',
+    handler_id BIGINT COMMENT '处理人ID',
+    handler_name VARCHAR(128) COMMENT '处理人姓名',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_return_no (return_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='退件表';
+
+-- =============================================
+-- 5. 库存中心
+-- =============================================
+
+CREATE TABLE inventory (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
+    available INT NOT NULL DEFAULT 0 COMMENT '可用库存',
+    locked INT NOT NULL DEFAULT 0 COMMENT '锁定库存',
+    inbound INT NOT NULL DEFAULT 0 COMMENT '在途库存',
+    safe_stock INT NOT NULL DEFAULT 0 COMMENT '安全库存',
+    warning_status VARCHAR(32) NOT NULL DEFAULT 'NORMAL' COMMENT '预警状态',
+    last_change_at DATETIME COMMENT '最后变动时间',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_inventory_sku_warehouse (sku_id, warehouse_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存表';
+
+CREATE TABLE inventory_log (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
+    source VARCHAR(64) NOT NULL COMMENT '变动来源',
+    related_order_id BIGINT COMMENT '关联订单ID',
+    related_order_no VARCHAR(64) COMMENT '关联订单号',
+    operator VARCHAR(128) NOT NULL COMMENT '操作人',
+    change_type VARCHAR(32) NOT NULL COMMENT '变动类型',
+    quantity INT NOT NULL COMMENT '变动数量',
+    before_qty INT NOT NULL COMMENT '变动前数量',
+    after_qty INT NOT NULL COMMENT '变动后数量',
+    reason VARCHAR(512) COMMENT '变动原因',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存流水表';
+
+-- =============================================
+-- 6. 供应商采购
+-- =============================================
+
+CREATE TABLE supplier (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '供应商名称',
+    company_name VARCHAR(256) COMMENT '公司名称',
+    contact VARCHAR(128) COMMENT '联系人',
+    phone VARCHAR(64) COMMENT '联系电话',
+    email VARCHAR(256) COMMENT '邮箱',
+    address VARCHAR(512) COMMENT '地址',
+    bank_info VARCHAR(512) COMMENT '银行信息',
+    tax_no VARCHAR(128) COMMENT '税号',
+    contract_no VARCHAR(128) COMMENT '合同编号',
+    settlement_type VARCHAR(32) NOT NULL DEFAULT 'CREDIT' COMMENT '结算方式',
+    rating VARCHAR(16) COMMENT '评级',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '供应商状态',
+    related_sku_count INT NOT NULL DEFAULT 0 COMMENT '关联SKU数量',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商表';
+
+CREATE TABLE supply_capability (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    lead_time INT NOT NULL DEFAULT 0 COMMENT '交期(天)',
+    moq INT NOT NULL DEFAULT 1 COMMENT '最小起订量',
+    unit VARCHAR(32) NOT NULL DEFAULT 'PCS' COMMENT '单位',
+    tier_prices JSON COMMENT '阶梯价格',
+    is_default TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否默认',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供货能力表';
+
+CREATE TABLE purchase_order (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    po_no VARCHAR(64) NOT NULL COMMENT '采购单号',
+    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
+    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
+    sku_count INT NOT NULL DEFAULT 0 COMMENT 'SKU数量',
+    amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '采购金额',
+    currency VARCHAR(8) NOT NULL DEFAULT 'CNY' COMMENT '币种',
+    tax_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '税额',
+    freight DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '运费',
+    expected_date DATE COMMENT '预计到货日期',
+    arrival_progress VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '到货进度',
+    status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '采购单状态',
+    remark VARCHAR(1024) COMMENT '备注',
+    creator_id BIGINT NOT NULL COMMENT '创建人ID',
+    creator_name VARCHAR(128) COMMENT '创建人姓名',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_po_no (po_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采购单表';
+
+CREATE TABLE purchase_order_item (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    purchase_order_id BIGINT NOT NULL COMMENT '采购单ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    sku VARCHAR(128) NOT NULL COMMENT 'SKU编码',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    qty INT NOT NULL COMMENT '采购数量',
+    price DECIMAL(20,4) NOT NULL COMMENT '采购单价',
+    subtotal DECIMAL(20,4) NOT NULL COMMENT '小计金额',
+    arrived_qty INT NOT NULL DEFAULT 0 COMMENT '已到货数量',
+    qualified_qty INT NOT NULL DEFAULT 0 COMMENT '合格数量',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采购单明细表';
+
+CREATE TABLE purchase_arrival (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    purchase_order_id BIGINT NOT NULL COMMENT '采购单ID',
+    arrival_no VARCHAR(64) NOT NULL COMMENT '到货单号',
+    arrived_at DATETIME NOT NULL COMMENT '到货时间',
+    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
+    operator_id BIGINT NOT NULL COMMENT '操作人ID',
+    operator_name VARCHAR(128) COMMENT '操作人姓名',
+    remark VARCHAR(1024) COMMENT '备注',
+    status VARCHAR(32) NOT NULL DEFAULT 'ARRIVED' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_arrival_no (arrival_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='到货确认表';
+
+CREATE TABLE purchase_arrival_item (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    arrival_id BIGINT NOT NULL COMMENT '到货单ID',
+    purchase_order_item_id BIGINT NOT NULL COMMENT '采购单明细ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    expected_qty INT NOT NULL COMMENT '预期数量',
+    actual_qty INT NOT NULL COMMENT '实际数量',
+    qualified_qty INT NOT NULL DEFAULT 0 COMMENT '合格数量',
+    unqualified_qty INT NOT NULL DEFAULT 0 COMMENT '不合格数量',
+    iqc_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '质检状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='到货明细表';
+
+CREATE TABLE iqc (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    iqc_no VARCHAR(64) NOT NULL COMMENT '质检单号',
+    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
+    arrival_id BIGINT NOT NULL COMMENT '到货单ID',
+    arrival_no VARCHAR(64) NOT NULL COMMENT '到货单号',
+    standard VARCHAR(256) COMMENT '质检标准',
+    qualified_qty INT NOT NULL DEFAULT 0 COMMENT '合格数量',
+    unqualified_qty INT NOT NULL DEFAULT 0 COMMENT '不合格数量',
+    result VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '质检结果',
+    inspector_id BIGINT COMMENT '质检员ID',
+    inspector_name VARCHAR(128) COMMENT '质检员姓名',
+    inspect_time DATETIME COMMENT '质检时间',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_iqc_no (iqc_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='来料质检表';
+
+CREATE TABLE replenishment_plan (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    warehouse_id BIGINT COMMENT '仓库ID',
+    avg_daily_sales DECIMAL(12,4) NOT NULL DEFAULT 0 COMMENT '日均销量',
+    safe_stock INT NOT NULL DEFAULT 0 COMMENT '安全库存',
+    current_stock INT NOT NULL DEFAULT 0 COMMENT '当前库存',
+    on_the_way INT NOT NULL DEFAULT 0 COMMENT '在途库存',
+    suggested_qty INT NOT NULL DEFAULT 0 COMMENT '建议补货量',
+    suggested_supplier_id VARCHAR(64) COMMENT '建议供应商ID',
+    suggested_supplier_name VARCHAR(128) COMMENT '建议供应商名称',
+    status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '计划状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='备货计划表';
+
+CREATE TABLE purchase_request (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    request_no VARCHAR(64) NOT NULL COMMENT '需求单号',
+    applicant_id BIGINT NOT NULL COMMENT '申请人ID',
+    applicant_name VARCHAR(128) NOT NULL COMMENT '申请人姓名',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    sku VARCHAR(128) NOT NULL COMMENT 'SKU编码',
+    qty INT NOT NULL COMMENT '需求数量',
+    reason VARCHAR(1024) COMMENT '需求原因',
+    urgency VARCHAR(32) NOT NULL DEFAULT 'NORMAL' COMMENT '紧急程度',
+    status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '需求状态',
+    related_order_id BIGINT COMMENT '关联订单ID',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_request_no (request_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='采购需求表';
+
+-- =============================================
+-- 7. 订单中心
+-- =============================================
+
+CREATE TABLE orders (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    channel_order_no VARCHAR(128) COMMENT '渠道订单号',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    order_status VARCHAR(32) NOT NULL DEFAULT 'CREATED' COMMENT '订单状态',
+    shipping_status VARCHAR(32) NOT NULL DEFAULT 'UNSHIPPED' COMMENT '发货状态',
+    payment_status VARCHAR(32) NOT NULL DEFAULT 'UNPAID' COMMENT '支付状态',
+    refund_status VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '退款状态',
+    exception_tag VARCHAR(64) COMMENT '异常标签',
+    priority VARCHAR(32) NOT NULL DEFAULT 'NORMAL' COMMENT '优先级',
+    paid_at DATETIME COMMENT '支付时间',
+    shipped_at DATETIME COMMENT '发货时间',
+    delivered_at DATETIME COMMENT '签收时间',
+    buyer_id VARCHAR(64) COMMENT '买家ID',
+    buyer VARCHAR(128) COMMENT '买家昵称',
+    buyer_email VARCHAR(256) COMMENT '买家邮箱',
+    buyer_phone VARCHAR(64) COMMENT '买家电话',
+    buyer_country VARCHAR(64) COMMENT '买家国家',
+    buyer_level VARCHAR(32) COMMENT '买家等级',
+    buyer_tags JSON COMMENT '买家标签',
+    buyer_register_time DATETIME COMMENT '注册时间',
+    buyer_order_count INT NOT NULL DEFAULT 0 COMMENT '历史订单数',
+    buyer_total_spent DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '历史消费金额',
+    receiver_name VARCHAR(128) NOT NULL COMMENT '收货人姓名',
+    receiver_phone VARCHAR(64) NOT NULL COMMENT '收货人电话',
+    receiver_country VARCHAR(64) NOT NULL COMMENT '收货国家',
+    receiver_state VARCHAR(128) COMMENT '收货省份',
+    receiver_city VARCHAR(128) COMMENT '收货城市',
+    receiver_district VARCHAR(128) COMMENT '收货区县',
+    receiver_postal_code VARCHAR(32) COMMENT '收货邮编',
+    receiver_address VARCHAR(512) NOT NULL COMMENT '详细地址',
+    receiver_address_lang VARCHAR(64) COMMENT '地址语言',
+    latitude DECIMAL(10,7) COMMENT '纬度',
+    longitude DECIMAL(10,7) COMMENT '经度',
+    transaction_id VARCHAR(128) COMMENT '交易流水号',
+    payment_method VARCHAR(64) COMMENT '支付方式',
+    payment_time DATETIME COMMENT '支付时间',
+    currency VARCHAR(8) NOT NULL DEFAULT 'USD' COMMENT '订单币种',
+    exchange_rate DECIMAL(20,6) NOT NULL DEFAULT 1 COMMENT '汇率',
+    order_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '商品金额',
+    order_amount_cny DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '人民币金额',
+    tax_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '税额',
+    shipping_fee DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '运费',
+    discount_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '折扣金额',
+    coupon_code VARCHAR(128) COMMENT '优惠券编码',
+    coupon_discount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '优惠券折扣',
+    actual_paid DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '实付金额',
+    refund_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '退款金额',
+    shipping_method VARCHAR(64) COMMENT '配送方式',
+    carrier_id BIGINT COMMENT '物流商ID',
+    carrier_name VARCHAR(128) COMMENT '物流商名称',
+    tracking_no VARCHAR(256) COMMENT '运单号',
+    tracking_url VARCHAR(512) COMMENT '追踪URL',
+    warehouse_id BIGINT COMMENT '仓库ID',
+    warehouse_location VARCHAR(128) COMMENT '库位',
+    estimated_delivery DATE COMMENT '预计送达日期',
+    weight DECIMAL(10,3) COMMENT '重量(kg)',
+    length DECIMAL(10,2) COMMENT '长度(cm)',
+    width DECIMAL(10,2) COMMENT '宽度(cm)',
+    height DECIMAL(10,2) COMMENT '高度(cm)',
+    utm_source VARCHAR(128) COMMENT 'UTM来源',
+    utm_medium VARCHAR(128) COMMENT 'UTM媒介',
+    utm_campaign VARCHAR(128) COMMENT 'UTM广告系列',
+    utm_content VARCHAR(256) COMMENT 'UTM内容',
+    utm_term VARCHAR(256) COMMENT 'UTM关键词',
+    referrer_url VARCHAR(1024) COMMENT 'referrer来源',
+    landing_page VARCHAR(512) COMMENT '落地页',
+    ip VARCHAR(64) COMMENT 'IP地址',
+    ip_country VARCHAR(64) COMMENT 'IP归属国',
+    device VARCHAR(64) COMMENT '设备类型',
+    browser VARCHAR(64) COMMENT '浏览器',
+    os VARCHAR(64) COMMENT '操作系统',
+    parent_order_id BIGINT COMMENT '母订单ID',
+    merge_order_id BIGINT COMMENT '合并订单ID',
+    related_order_id BIGINT COMMENT '关联订单ID',
+    original_order_id BIGINT COMMENT '原订单ID(补发用)',
+    handler_id BIGINT COMMENT '处理人ID',
+    handler_name VARCHAR(128) COMMENT '处理人姓名',
+    handler_group VARCHAR(64) COMMENT '处理组',
+    order_tags JSON COMMENT '订单标签',
+    internal_remark TEXT COMMENT '内部备注',
+    buyer_remark TEXT COMMENT '买家备注',
+    item_count INT NOT NULL DEFAULT 0 COMMENT '商品数量',
+    total_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '订单总金额',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_order_no (order_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单主表';
+
+CREATE TABLE order_item (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    sku VARCHAR(128) NOT NULL COMMENT 'SKU编码',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    product_image VARCHAR(512) COMMENT '商品图片',
+    category_id VARCHAR(64) COMMENT '类目ID',
+    category_name VARCHAR(128) COMMENT '类目名称',
+    specs JSON COMMENT '规格信息',
+    barcode VARCHAR(128) COMMENT '条形码',
+    qty INT NOT NULL COMMENT '数量',
+    price DECIMAL(20,4) NOT NULL COMMENT '单价',
+    cost_price DECIMAL(20,4) NOT NULL COMMENT '成本价',
+    profit DECIMAL(20,4) COMMENT '利润',
+    profit_rate DECIMAL(8,4) COMMENT '利润率',
+    subtotal DECIMAL(20,4) NOT NULL COMMENT '小计金额',
+    weight DECIMAL(10,3) COMMENT '重量',
+    available INT NOT NULL DEFAULT 0 COMMENT '可用库存',
+    locked INT NOT NULL DEFAULT 0 COMMENT '锁定库存',
+    in_transit INT NOT NULL DEFAULT 0 COMMENT '在途库存',
+    reserved_qty INT NOT NULL DEFAULT 0 COMMENT '预留数量',
+    shipped_qty INT NOT NULL DEFAULT 0 COMMENT '已发货数量',
+    returned_qty INT NOT NULL DEFAULT 0 COMMENT '已退货数量',
+    gift_flag TINYINT(1) NOT NULL DEFAULT 0 COMMENT '赠品标记',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单明细表';
+
+CREATE TABLE order_status_event (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    status VARCHAR(64) NOT NULL COMMENT '状态',
+    time DATETIME NOT NULL COMMENT '时间',
+    title VARCHAR(256) NOT NULL COMMENT '标题',
+    summary VARCHAR(1024) COMMENT '摘要',
+    type VARCHAR(64) NOT NULL COMMENT '类型',
+    operator VARCHAR(128) COMMENT '操作人',
+    operator_role VARCHAR(64) COMMENT '操作人角色',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单状态事件表';
+
+CREATE TABLE order_operation_log (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    order_id BIGINT COMMENT '订单ID',
+    module VARCHAR(64) NOT NULL COMMENT '模块',
+    type VARCHAR(64) NOT NULL COMMENT '操作类型',
+    object_id VARCHAR(64) COMMENT '对象ID',
+    time DATETIME NOT NULL COMMENT '操作时间',
+    title VARCHAR(256) NOT NULL COMMENT '操作标题',
+    content TEXT COMMENT '操作内容',
+    operator VARCHAR(128) NOT NULL COMMENT '操作人',
+    operator_role VARCHAR(64) COMMENT '操作人角色',
+    result VARCHAR(64) COMMENT '操作结果',
+    source_ip VARCHAR(64) COMMENT '来源IP',
+    before_value TEXT COMMENT '变更前值',
+    after_value TEXT COMMENT '变更后值',
+    remark TEXT COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单操作日志表';
+
+CREATE TABLE shipping_order (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    shipment_no VARCHAR(64) NOT NULL COMMENT '发货单号',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
+    items JSON NOT NULL COMMENT '发货商品明细',
+    sku_count INT NOT NULL DEFAULT 0 COMMENT 'SKU数量',
+    expected_qty INT NOT NULL DEFAULT 0 COMMENT '预期数量',
+    actual_qty INT NOT NULL DEFAULT 0 COMMENT '实际数量',
+    carrier_id BIGINT COMMENT '物流商ID',
+    carrier_name VARCHAR(128) COMMENT '物流商名称',
+    tracking_no VARCHAR(256) COMMENT '运单号',
+    shipping_status VARCHAR(32) NOT NULL DEFAULT 'PENDING_PICK' COMMENT '发货状态',
+    return_status VARCHAR(32) NOT NULL DEFAULT 'NOT_RETURNED' COMMENT '回传状态',
+    return_tracking_no VARCHAR(256) COMMENT '退件运单号',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    shipped_at DATETIME COMMENT '发货时间',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_shipment_no (shipment_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='发货单表';
+
+-- =============================================
+-- 8. 售后中心
+-- =============================================
+
+CREATE TABLE after_sale (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    after_sale_no VARCHAR(64) NOT NULL COMMENT '售后单号',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    buyer_id VARCHAR(64) COMMENT '买家ID',
+    buyer VARCHAR(128) NOT NULL COMMENT '买家姓名',
+    type VARCHAR(32) NOT NULL COMMENT '售后类型',
+    amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '申请金额',
+    audit_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '审核状态',
+    refund_status VARCHAR(32) NOT NULL DEFAULT 'NONE' COMMENT '退款状态',
+    reason VARCHAR(1024) NOT NULL COMMENT '申请原因',
+    responsibility VARCHAR(64) COMMENT '责任方',
+    refund_amount DECIMAL(20,4) COMMENT '实际退款金额',
+    refund_method VARCHAR(64) COMMENT '退款方式',
+    return_tracking_no VARCHAR(256) COMMENT '退货运单号',
+    return_carrier VARCHAR(128) COMMENT '退货物流商',
+    resend_warehouse_id BIGINT COMMENT '补发仓库ID',
+    resend_sku_id BIGINT COMMENT '补发SKU ID',
+    resend_order_id BIGINT COMMENT '补发订单ID',
+    audit_remark TEXT COMMENT '审核备注',
+    images JSON COMMENT '图片列表',
+    handler_id BIGINT COMMENT '处理人ID',
+    handler_name VARCHAR(128) COMMENT '处理人姓名',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_after_sale_no (after_sale_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='售后表';
+
+-- =============================================
+-- 9. 财务中心
+-- =============================================
+
+CREATE TABLE finance_payment (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    order_id BIGINT COMMENT '订单ID',
+    payment_no VARCHAR(64) NOT NULL COMMENT '收款单号',
+    channel_order_no VARCHAR(128) COMMENT '渠道订单号',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    shop_name VARCHAR(256) COMMENT '店铺名称',
+    currency VARCHAR(8) NOT NULL COMMENT '币种',
+    amount DECIMAL(20,4) NOT NULL COMMENT '收款金额',
+    fee DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '手续费',
+    pay_method VARCHAR(64) COMMENT '支付方式',
+    pay_time DATETIME COMMENT '支付时间',
+    transaction_no VARCHAR(128) COMMENT '交易流水号',
+    reconcile_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '对账状态',
+    reconcile_time DATETIME COMMENT '对账时间',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_payment_no (payment_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='收款表';
+
+CREATE TABLE finance_refund (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    refund_no VARCHAR(64) NOT NULL COMMENT '退款单号',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    channel_order_no VARCHAR(128) COMMENT '渠道订单号',
+    currency VARCHAR(8) NOT NULL COMMENT '币种',
+    refund_amount DECIMAL(20,4) NOT NULL COMMENT '退款金额',
+    refund_method VARCHAR(64) COMMENT '退款方式',
+    refund_time DATETIME COMMENT '退款时间',
+    refund_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '退款状态',
+    channel_refund_no VARCHAR(128) COMMENT '渠道退款单号',
+    reason VARCHAR(1024) COMMENT '退款原因',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_refund_no (refund_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='退款表';
+
+CREATE TABLE supplier_settlement (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    settlement_no VARCHAR(64) NOT NULL COMMENT '结算单号',
+    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
+    period VARCHAR(32) NOT NULL COMMENT '结算周期',
+    purchase_order_ids JSON COMMENT '关联采购单ID列表',
+    payable_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '应付金额',
+    paid_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '已付金额',
+    status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '结算状态',
+    paid_at DATETIME COMMENT '支付时间',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_settlement_no (settlement_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商结算表';
+
+CREATE TABLE invoice (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    invoice_no VARCHAR(64) NOT NULL COMMENT '发票号',
+    invoice_type VARCHAR(32) NOT NULL COMMENT '发票类型',
+    order_id BIGINT COMMENT '订单ID',
+    buyer_name VARCHAR(256) NOT NULL COMMENT '购买方名称',
+    buyer_tax_no VARCHAR(128) COMMENT '购买方税号',
+    seller_name VARCHAR(256) NOT NULL COMMENT '销售方名称',
+    amount DECIMAL(20,4) NOT NULL COMMENT '发票金额',
+    tax_rate DECIMAL(8,4) NOT NULL COMMENT '税率',
+    tax_amount DECIMAL(20,4) NOT NULL COMMENT '税额',
+    invoice_date DATE NOT NULL COMMENT '开票日期',
+    status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '发票状态',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_invoice_no (invoice_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='发票表';
+
+-- =============================================
+-- 10. 客服中心
+-- =============================================
+
+CREATE TABLE ticket (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    ticket_no VARCHAR(64) NOT NULL COMMENT '工单编号',
+    title VARCHAR(256) NOT NULL COMMENT '工单标题',
+    type VARCHAR(32) NOT NULL COMMENT '工单类型',
+    priority VARCHAR(32) NOT NULL DEFAULT 'MEDIUM' COMMENT '优先级',
+    status VARCHAR(32) NOT NULL DEFAULT 'OPEN' COMMENT '工单状态',
+    creator VARCHAR(128) NOT NULL COMMENT '创建人',
+    assignee_id BIGINT COMMENT '受理人ID',
+    assignee_name VARCHAR(128) COMMENT '受理人姓名',
+    related_order_id BIGINT COMMENT '关联订单ID',
+    related_order_no VARCHAR(64) COMMENT '关联订单号',
+    content TEXT NOT NULL COMMENT '工单内容',
+    images JSON COMMENT '图片列表',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_ticket_no (ticket_no)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='工单表';
+
+CREATE TABLE satisfaction (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    order_no VARCHAR(64) NOT NULL COMMENT '订单号',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    source VARCHAR(64) COMMENT '来源',
+    buyer VARCHAR(128) COMMENT '买家',
+    product_title VARCHAR(256) COMMENT '商品标题',
+    rating INT NOT NULL COMMENT '评分',
+    content TEXT COMMENT '评价内容',
+    reply TEXT COMMENT '回复内容',
+    handle_status VARCHAR(32) NOT NULL DEFAULT 'PENDING' COMMENT '处理状态',
+    cs_id BIGINT COMMENT '客服ID',
+    cs_name VARCHAR(128) COMMENT '客服姓名',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='满意度评价表';
+
+-- =============================================
+-- 11. AI客服
+-- =============================================
+
+CREATE TABLE ai_channel (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '渠道名称',
+    code VARCHAR(64) NOT NULL COMMENT '渠道编码',
+    icon VARCHAR(64) COMMENT '图标',
+    enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
+    robot_name VARCHAR(128) COMMENT '机器人名称',
+    robot_enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '机器人是否启用',
+    auto_transfer_threshold INT NOT NULL DEFAULT 300 COMMENT '自动转人工阈值(秒)',
+    priority INT NOT NULL DEFAULT 0 COMMENT '优先级',
+    welcome_message TEXT COMMENT '欢迎语',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id),
+    UNIQUE KEY uk_ai_channel_code (code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI渠道配置表';
+
+CREATE TABLE knowledge_category (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '分类名称',
+    parent_id VARCHAR(64) COMMENT '父分类ID',
+    sort_order INT NOT NULL DEFAULT 0 COMMENT '排序',
+    count INT NOT NULL DEFAULT 0 COMMENT '条目数量',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='知识库分类表';
+
+CREATE TABLE knowledge_base (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    category_id BIGINT NOT NULL COMMENT '分类ID',
+    category_name VARCHAR(128) NOT NULL COMMENT '分类名称',
+    keywords JSON COMMENT '关键词列表',
+    question VARCHAR(512) NOT NULL COMMENT '问题',
+    answer TEXT NOT NULL COMMENT '回答',
+    clicks INT NOT NULL DEFAULT 0 COMMENT '点击次数',
+    ai_score DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT 'AI评分',
+    status VARCHAR(32) NOT NULL DEFAULT 'ENABLED' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='知识库表';
+
+CREATE TABLE auto_reply_rule (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '规则名称',
+    priority INT NOT NULL DEFAULT 0 COMMENT '优先级',
+    trigger_type VARCHAR(32) NOT NULL COMMENT '触发类型',
+    keywords JSON COMMENT '关键词列表',
+    match_mode VARCHAR(32) NOT NULL DEFAULT 'CONTAIN' COMMENT '匹配模式',
+    responses JSON NOT NULL COMMENT '回复列表',
+    status VARCHAR(32) NOT NULL DEFAULT 'ENABLED' COMMENT '规则状态',
+    hit_count INT NOT NULL DEFAULT 0 COMMENT '命中次数',
+    accuracy DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '准确率',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='自动回复规则表';
+
+CREATE TABLE chat_session (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    visitor_id BIGINT NOT NULL COMMENT '访客ID',
+    visitor_name VARCHAR(128) COMMENT '访客名称',
+    visitor_avatar VARCHAR(512) COMMENT '访客头像',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    shop_name VARCHAR(256) COMMENT '店铺名称',
+    ai_handled TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'AI处理标记',
+    agent_id BIGINT COMMENT '客服ID',
+    agent_name VARCHAR(128) COMMENT '客服名称',
+    satisfaction INT COMMENT '满意度评分',
+    status VARCHAR(32) NOT NULL DEFAULT 'WAITING' COMMENT '会话状态',
+    source VARCHAR(64) COMMENT '来源',
+    device VARCHAR(64) COMMENT '设备',
+    location VARCHAR(128) COMMENT '位置',
+    intent VARCHAR(128) COMMENT '意图',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    ended_at DATETIME COMMENT '结束时间',
+    last_message_at DATETIME NOT NULL COMMENT '最后消息时间',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI会话表';
+
+CREATE TABLE chat_message (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    session_id BIGINT NOT NULL COMMENT '会话ID',
+    role VARCHAR(32) NOT NULL COMMENT '发送者角色',
+    content TEXT NOT NULL COMMENT '消息内容',
+    attachments JSON COMMENT '附件列表',
+    timestamp DATETIME NOT NULL COMMENT '发送时间',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI消息表';
+
+CREATE TABLE service_performance (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    agent_id BIGINT NOT NULL COMMENT '客服ID',
+    agent_name VARCHAR(128) NOT NULL COMMENT '客服名称',
+    agent_avatar VARCHAR(512) COMMENT '客服头像',
+    department VARCHAR(128) COMMENT '部门',
+    handle_count INT NOT NULL DEFAULT 0 COMMENT '处理数量',
+    ai_assist_count INT NOT NULL DEFAULT 0 COMMENT 'AI辅助次数',
+    avg_response_time DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '平均响应时间(秒)',
+    avg_first_response_time DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '平均首次响应时间(秒)',
+    satisfaction DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '满意度',
+    solve_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '解决率',
+    date DATE NOT NULL COMMENT '统计日期',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客服绩效表';
+
+-- =============================================
+-- 12. 营销中心
+-- =============================================
+
+CREATE TABLE promotion (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '活动名称',
+    type VARCHAR(32) NOT NULL COMMENT '活动类型',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    shop_name VARCHAR(256) COMMENT '店铺名称',
+    start_time DATETIME NOT NULL COMMENT '开始时间',
+    end_time DATETIME NOT NULL COMMENT '结束时间',
+    status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '活动状态',
+    discount VARCHAR(64) COMMENT '折扣信息',
+    min_amount DECIMAL(20,4) COMMENT '最低消费金额',
+    products JSON COMMENT '参与商品列表',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='促销活动表';
+
+CREATE TABLE coupon (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    name VARCHAR(128) NOT NULL COMMENT '优惠券名称',
+    coupon_type VARCHAR(32) NOT NULL COMMENT '优惠券类型',
+    value DECIMAL(20,4) NOT NULL COMMENT '优惠值',
+    min_amount DECIMAL(20,4) NOT NULL DEFAULT 0 COMMENT '最低消费金额',
+    total_count INT NOT NULL DEFAULT 0 COMMENT '发放总量',
+    used_count INT NOT NULL DEFAULT 0 COMMENT '已使用数量',
+    used_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '使用率',
+    valid_start DATETIME NOT NULL COMMENT '有效期开始',
+    valid_end DATETIME NOT NULL COMMENT '有效期结束',
+    status VARCHAR(32) NOT NULL DEFAULT 'DRAFT' COMMENT '优惠券状态',
+    channels JSON COMMENT '可用渠道列表',
+    remark VARCHAR(1024) COMMENT '备注',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    created_by VARCHAR(64) COMMENT '创建人',
+    updated_by VARCHAR(64) COMMENT '更新人',
+    deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '软删除标记',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='优惠券表';
+
+CREATE TABLE price_watch (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    channel_id BIGINT NOT NULL COMMENT '渠道ID',
+    shop_name VARCHAR(256) COMMENT '店铺名称',
+    local_price DECIMAL(20,4) NOT NULL COMMENT '本地价格',
+    competitor_price DECIMAL(20,4) NOT NULL COMMENT '竞品价格',
+    price_diff DECIMAL(20,4) NOT NULL COMMENT '价格差异',
+    competitor_name VARCHAR(128) COMMENT '竞品名称',
+    competitor_url VARCHAR(512) COMMENT '竞品链接',
+    last_update DATETIME NOT NULL COMMENT '最后更新时间',
+    alert_status VARCHAR(32) NOT NULL DEFAULT 'NORMAL' COMMENT '预警状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='价格监控表';
+
+CREATE TABLE supplier_performance (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
+    supplier_name VARCHAR(128) NOT NULL COMMENT '供应商名称',
+    contact VARCHAR(128) COMMENT '联系人',
+    phone VARCHAR(64) COMMENT '联系电话',
+    delivery_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '交货率',
+    quality_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '质量合格率',
+    response_time DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '响应时间(小时)',
+    return_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '退货率',
+    price_score DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '价格评分',
+    overall_score DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '综合评分',
+    rating_level VARCHAR(16) COMMENT '评级',
+    status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE' COMMENT '状态',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商绩效表';
+
+-- =============================================
+-- 13. 报表中心
+-- =============================================
+
+CREATE TABLE inventory_turnover (
+    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+    sku_id BIGINT NOT NULL COMMENT 'SKU ID',
+    product_title VARCHAR(256) NOT NULL COMMENT '商品标题',
+    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
+    turnover_days INT NOT NULL DEFAULT 0 COMMENT '周转天数',
+    sales_qty INT NOT NULL DEFAULT 0 COMMENT '销售数量',
+    avg_stock DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '平均库存',
+    alert_status VARCHAR(32) NOT NULL DEFAULT 'NORMAL' COMMENT '预警状态',
+    date DATE NOT NULL COMMENT '统计日期',
+    tenant_id VARCHAR(64) NOT NULL DEFAULT 'DEFAULT' COMMENT '租户ID',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存周转表';
+
+-- =============================================
+-- 初始化数据
+-- =============================================
+
+-- 初始化超级管理员 (password: admin123)
+INSERT INTO sys_user (username, password_hash, email, name, role, role_label, workspace, status, tenant_id) VALUES
+('admin', '$2a$10$AKKZ1KuZW6lsiXk3ecbrn.Y5O8yucLF1/he37JoLsibfh3cVE4cSS', 'admin@oms.com', '系统管理员', 'ADMIN', '超级管理员', '全栈', 'ACTIVE', 'DEFAULT');
+
+-- 初始化默认角色
+INSERT INTO sys_role (name, description, permissions, bound_user_count, status, tenant_id) VALUES
+('ADMIN', '超级管理员', '["*:*:*"]', 1, 'ACTIVE', 'DEFAULT'),
+('MANAGER', '运营经理', '["product:*", "order:*", "inventory:*"]', 0, 'ACTIVE', 'DEFAULT'),
+('OPERATOR', '运营专员', '["product:read", "order:*"]', 0, 'ACTIVE', 'DEFAULT'),
+('PROCUREMENT', '采购员', '["purchase:*"]', 0, 'ACTIVE', 'DEFAULT'),
+('WAREHOUSE', '仓库管理员', '["inventory:*", "warehouse:*"]', 0, 'ACTIVE', 'DEFAULT'),
+('FINANCE', '财务', '["finance:*"]', 0, 'ACTIVE', 'DEFAULT'),
+('CUSTOMER_SERVICE', '客服', '["after-sale:*", "ticket:*"]', 0, 'ACTIVE', 'DEFAULT');

+ 1 - 1
backend/src/main/java/com/oms/aspect/OperationLogAspect.java

@@ -75,7 +75,7 @@ public class OperationLogAspect {
         String module = className.replace("Controller", "");
         String type = methodName.toUpperCase();
 
-        String objectId = extractObjectId(joinPoint.getArgs());
+        Long objectId = extractObjectId(joinPoint.getArgs());
 
         SysOperationLog logEntry = new SysOperationLog();
         logEntry.setModule(module);

+ 18 - 2
backend/src/main/java/com/oms/config/SecurityConfig.java

@@ -1,16 +1,19 @@
 package com.oms.config;
 
+import com.oms.config.security.CustomUserDetailsService;
 import com.oms.config.security.JwtAuthenticationFilter;
 import lombok.RequiredArgsConstructor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
 import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.web.cors.CorsConfiguration;
@@ -27,16 +30,29 @@ import java.util.List;
 public class SecurityConfig {
 
     private final JwtAuthenticationFilter jwtAuthenticationFilter;
+    private final CustomUserDetailsService customUserDetailsService;
+    private final PasswordEncoder passwordEncoder;
+
+    @Bean
+    public DaoAuthenticationProvider authenticationProvider() {
+        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+        authProvider.setUserDetailsService(customUserDetailsService);
+        authProvider.setPasswordEncoder(passwordEncoder);
+        return authProvider;
+    }
 
     @Bean
     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         http
             .csrf(AbstractHttpConfigurer::disable)
             .cors(cors -> cors.configurationSource(corsConfigurationSource()))
+            .authenticationProvider(authenticationProvider())
             .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
             .authorizeHttpRequests(auth -> auth
                 .requestMatchers(
                     "/auth/**",
+                    "/debug/**",
+                    "/test/**",
                     "/doc.html",
                     "/v3/api-docs/**",
                     "/swagger-ui/**",
@@ -65,7 +81,7 @@ public class SecurityConfig {
     }
 
     @Bean
-    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
-        return config.getAuthenticationManager();
+    public AuthenticationManager authenticationManager() throws Exception {
+        return new org.springframework.security.authentication.ProviderManager(authenticationProvider());
     }
 }

+ 10 - 10
backend/src/main/java/com/oms/service/CustomUserDetailsService.java → backend/src/main/java/com/oms/config/security/CustomUserDetailsService.java

@@ -1,17 +1,18 @@
-package com.oms.service;
+package com.oms.config.security;
 
 import com.oms.entity.SysUser;
+import com.oms.service.SysUserService;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
 
 import java.util.Collections;
 
-@Service
+@Component
 @RequiredArgsConstructor
 public class CustomUserDetailsService implements UserDetailsService {
 
@@ -19,20 +20,19 @@ public class CustomUserDetailsService implements UserDetailsService {
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-        SysUser user = sysUserService.loadUserByUsername(username);
-
-        if (user == null) {
+        SysUser sysUser = sysUserService.getByUsername(username);
+        if (sysUser == null) {
             throw new UsernameNotFoundException("用户不存在: " + username);
         }
 
         return new User(
-                user.getUsername(),
-                user.getPasswordHash(),
-                "ACTIVE".equals(user.getStatus()),
+                sysUser.getUsername(),
+                sysUser.getPasswordHash(),
+                "ACTIVE".equals(sysUser.getStatus()),
                 true,
                 true,
                 true,
-                Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole()))
+                Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + sysUser.getRole()))
         );
     }
 }

+ 1 - 1
backend/src/main/java/com/oms/config/security/JwtAuthenticationFilter.java

@@ -1,6 +1,6 @@
 package com.oms.config.security;
 
-import com.oms.service.CustomUserDetailsService;
+import com.oms.config.security.CustomUserDetailsService;
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;

+ 88 - 0
backend/src/main/java/com/oms/controller/AfterSaleController.java

@@ -0,0 +1,88 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.AfterSaleDTO;
+import com.oms.entity.AfterSale;
+import com.oms.service.AfterSaleService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/after-sale/after-sales")
+@RequiredArgsConstructor
+public class AfterSaleController {
+
+    private final AfterSaleService afterSaleService;
+
+    @GetMapping
+    public ApiResponse<List<AfterSale>> getAfterSales(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(afterSaleService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<AfterSale>> getAll() {
+        return ApiResponse.success(afterSaleService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<AfterSaleDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(afterSaleService.getDtoById(id));
+    }
+
+    @GetMapping("/after-sale-no/{afterSaleNo}")
+    public ApiResponse<AfterSale> getByAfterSaleNo(@PathVariable String afterSaleNo) {
+        return ApiResponse.success(afterSaleService.getByAfterSaleNo(afterSaleNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody AfterSale afterSale) {
+        return ApiResponse.success(afterSaleService.save(afterSale));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody AfterSale afterSale) {
+        afterSale.setId(id);
+        afterSaleService.update(afterSale);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        afterSaleService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/approve")
+    public ApiResponse<Void> approve(@PathVariable Long id, @RequestParam(required = false) String remark) {
+        afterSaleService.approve(id, remark, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/reject")
+    public ApiResponse<Void> reject(@PathVariable Long id, @RequestParam String reason) {
+        afterSaleService.reject(id, reason, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/process-refund")
+    public ApiResponse<Void> processRefund(@PathVariable Long id) {
+        afterSaleService.processRefund(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/submit-return-tracking")
+    public ApiResponse<Void> submitReturnTracking(@PathVariable Long id, @RequestParam String carrier, @RequestParam String trackingNo) {
+        afterSaleService.submitReturnTracking(id, carrier, trackingNo, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/confirm-receipt")
+    public ApiResponse<Void> confirmReceipt(@PathVariable Long id) {
+        afterSaleService.confirmReceipt(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+}

+ 51 - 0
backend/src/main/java/com/oms/controller/AiChannelController.java

@@ -0,0 +1,51 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.AiChannelDTO;
+import com.oms.entity.AiChannel;
+import com.oms.service.AiChannelService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/channels")
+@RequiredArgsConstructor
+public class AiChannelController {
+    private final AiChannelService aiChannelService;
+
+    @GetMapping
+    public ApiResponse<List<AiChannel>> getAiChannels(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(aiChannelService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<AiChannel>> getAll() {
+        return ApiResponse.success(aiChannelService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<AiChannelDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(aiChannelService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody AiChannel aiChannel) {
+        return ApiResponse.success(aiChannelService.save(aiChannel));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody AiChannel aiChannel) {
+        aiChannel.setId(id);
+        aiChannelService.update(aiChannel);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { aiChannelService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/toggle-enabled")
+    public ApiResponse<Void> toggleEnabled(@PathVariable Long id, @RequestParam boolean enabled) { aiChannelService.toggleEnabled(id, enabled); return ApiResponse.success(null); }
+}

+ 1 - 1
backend/src/main/java/com/oms/controller/AuthController.java

@@ -49,7 +49,7 @@ public class AuthController {
     public ApiResponse<LoginResponse> refreshToken() {
         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
         String username = authentication.getName();
-        String token = jwtTokenProvider.generateToken(username);
+        String token = jwtTokenProvider.generateToken(username, new java.util.HashMap<>());
         
         LoginResponse response = new LoginResponse();
         response.setToken(token);

+ 52 - 0
backend/src/main/java/com/oms/controller/AutoReplyRuleController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.AutoReplyRuleDTO;
+import com.oms.entity.AutoReplyRule;
+import com.oms.service.AutoReplyRuleService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/auto-reply-rules")
+@RequiredArgsConstructor
+public class AutoReplyRuleController {
+    private final AutoReplyRuleService autoReplyRuleService;
+
+    @GetMapping
+    public ApiResponse<List<AutoReplyRule>> getAutoReplyRules(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(autoReplyRuleService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<AutoReplyRule>> getAll() {
+        return ApiResponse.success(autoReplyRuleService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<AutoReplyRuleDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(autoReplyRuleService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody AutoReplyRule autoReplyRule) {
+        return ApiResponse.success(autoReplyRuleService.save(autoReplyRule));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody AutoReplyRule autoReplyRule) {
+        autoReplyRule.setId(id);
+        autoReplyRuleService.update(autoReplyRule);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        autoReplyRuleService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 81 - 0
backend/src/main/java/com/oms/controller/ChannelController.java

@@ -0,0 +1,81 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ChannelDTO;
+import com.oms.entity.Channel;
+import com.oms.service.ChannelService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/channel/channels")
+@RequiredArgsConstructor
+public class ChannelController {
+
+    private final ChannelService channelService;
+
+    @GetMapping
+    public ApiResponse<List<Channel>> getChannels(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(channelService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Channel>> getAll() {
+        return ApiResponse.success(channelService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ChannelDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(channelService.getDtoById(id));
+    }
+
+    @GetMapping("/code/{channelCode}")
+    public ApiResponse<Channel> getByChannelCode(@PathVariable String channelCode) {
+        return ApiResponse.success(channelService.getByChannelCode(channelCode));
+    }
+
+    @GetMapping("/token-status/{tokenStatus}")
+    public ApiResponse<List<Channel>> getByTokenStatus(@PathVariable String tokenStatus) {
+        return ApiResponse.success(channelService.getByTokenStatus(tokenStatus));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Channel channel) {
+        return ApiResponse.success(channelService.save(channel));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Channel channel) {
+        channel.setId(id);
+        channelService.update(channel);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        channelService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/test-connection")
+    public ApiResponse<Void> testConnection(@PathVariable Long id) {
+        channelService.testConnection(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/toggle-sync")
+    public ApiResponse<Void> toggleSync(@PathVariable Long id, @RequestParam boolean enabled) {
+        channelService.toggleSync(id, enabled);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/reauthorize")
+    public ApiResponse<Void> reauthorize(@PathVariable Long id) {
+        channelService.reauthorize(id);
+        return ApiResponse.success(null);
+    }
+}

+ 78 - 0
backend/src/main/java/com/oms/controller/ChannelMappingController.java

@@ -0,0 +1,78 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.entity.ChannelMapping;
+import com.oms.service.ChannelMappingService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/product/channel-mappings")
+@RequiredArgsConstructor
+public class ChannelMappingController {
+
+    private final ChannelMappingService channelMappingService;
+
+    @GetMapping
+    public ApiResponse<List<ChannelMapping>> getMappings(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(channelMappingService.getMappings(page, size).getRecords());
+    }
+
+    @GetMapping("/product/{productId}")
+    public ApiResponse<List<ChannelMapping>> getByProductId(@PathVariable Long productId) {
+        return ApiResponse.success(channelMappingService.getByProductId(productId));
+    }
+
+    @GetMapping("/sku/{skuId}")
+    public ApiResponse<List<ChannelMapping>> getBySkuId(@PathVariable Long skuId) {
+        return ApiResponse.success(channelMappingService.getBySkuId(skuId));
+    }
+
+    @GetMapping("/channel/{channelId}")
+    public ApiResponse<List<ChannelMapping>> getByChannelId(@PathVariable Long channelId) {
+        return ApiResponse.success(channelMappingService.getByChannelId(channelId));
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ChannelMapping> getById(@PathVariable Long id) {
+        return ApiResponse.success(channelMappingService.getById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ChannelMapping mapping) {
+        return ApiResponse.success(channelMappingService.save(mapping));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ChannelMapping mapping) {
+        mapping.setId(id);
+        channelMappingService.update(mapping);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        channelMappingService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/validate")
+    public ApiResponse<String> validate(@PathVariable Long id) {
+        return ApiResponse.success(channelMappingService.validate(id));
+    }
+
+    @PostMapping("/{id}/publish")
+    public ApiResponse<Void> publish(@PathVariable Long id) {
+        channelMappingService.publish(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/auto-map")
+    public ApiResponse<java.util.List<Long>> autoMap(@RequestParam Long productId, @RequestParam Long channelId) {
+        return ApiResponse.success(channelMappingService.autoMap(productId, channelId));
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/ChatMessageController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ChatMessageDTO;
+import com.oms.entity.ChatMessage;
+import com.oms.service.ChatMessageService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/chat-messages")
+@RequiredArgsConstructor
+public class ChatMessageController {
+    private final ChatMessageService chatMessageService;
+
+    @GetMapping
+    public ApiResponse<List<ChatMessage>> getChatMessages(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(chatMessageService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<ChatMessage>> getAll() {
+        return ApiResponse.success(chatMessageService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ChatMessageDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(chatMessageService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ChatMessage chatMessage) {
+        return ApiResponse.success(chatMessageService.save(chatMessage));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ChatMessage chatMessage) {
+        chatMessage.setId(id);
+        chatMessageService.update(chatMessage);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        chatMessageService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/ChatSessionController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ChatSessionDTO;
+import com.oms.entity.ChatSession;
+import com.oms.service.ChatSessionService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/chat-sessions")
+@RequiredArgsConstructor
+public class ChatSessionController {
+    private final ChatSessionService chatSessionService;
+
+    @GetMapping
+    public ApiResponse<List<ChatSession>> getChatSessions(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(chatSessionService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<ChatSession>> getAll() {
+        return ApiResponse.success(chatSessionService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ChatSessionDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(chatSessionService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ChatSession chatSession) {
+        return ApiResponse.success(chatSessionService.save(chatSession));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ChatSession chatSession) {
+        chatSession.setId(id);
+        chatSessionService.update(chatSession);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        chatSessionService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 53 - 0
backend/src/main/java/com/oms/controller/CouponController.java

@@ -0,0 +1,53 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.CouponDTO;
+import com.oms.entity.Coupon;
+import com.oms.service.CouponService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/marketing/coupons")
+@RequiredArgsConstructor
+public class CouponController {
+    private final CouponService couponService;
+
+    @GetMapping
+    public ApiResponse<List<Coupon>> getCoupons(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(couponService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Coupon>> getAll() {
+        return ApiResponse.success(couponService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<CouponDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(couponService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Coupon coupon) {
+        return ApiResponse.success(couponService.save(coupon));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Coupon coupon) {
+        coupon.setId(id);
+        couponService.update(coupon);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { couponService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/issue")
+    public ApiResponse<Void> issue(@PathVariable Long id, @RequestParam int qty) { couponService.issue(id, qty); return ApiResponse.success(null); }
+    @PostMapping("/{id}/use")
+    public ApiResponse<Void> use(@PathVariable Long id) { couponService.use(id); return ApiResponse.success(null); }
+}

+ 22 - 0
backend/src/main/java/com/oms/controller/DashboardController.java

@@ -0,0 +1,22 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.service.DashboardService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/dashboard")
+@RequiredArgsConstructor
+public class DashboardController {
+    private final DashboardService dashboardService;
+
+    @GetMapping("/overview")
+    public ApiResponse<Map<String, Object>> getOverview() {
+        return ApiResponse.success(dashboardService.getOverview());
+    }
+}

+ 64 - 0
backend/src/main/java/com/oms/controller/FinancePaymentController.java

@@ -0,0 +1,64 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.FinancePaymentDTO;
+import com.oms.entity.FinancePayment;
+import com.oms.service.FinancePaymentService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/finance/payments")
+@RequiredArgsConstructor
+public class FinancePaymentController {
+
+    private final FinancePaymentService financePaymentService;
+
+    @GetMapping
+    public ApiResponse<List<FinancePayment>> getPayments(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(financePaymentService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<FinancePayment>> getAll() {
+        return ApiResponse.success(financePaymentService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<FinancePaymentDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(financePaymentService.getDtoById(id));
+    }
+
+    @GetMapping("/payment-no/{paymentNo}")
+    public ApiResponse<FinancePayment> getByPaymentNo(@PathVariable String paymentNo) {
+        return ApiResponse.success(financePaymentService.getByPaymentNo(paymentNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody FinancePayment payment) {
+        return ApiResponse.success(financePaymentService.save(payment));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody FinancePayment payment) {
+        payment.setId(id);
+        financePaymentService.update(payment);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        financePaymentService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/reconcile")
+    public ApiResponse<Void> reconcile(@PathVariable Long id) {
+        financePaymentService.reconcile(id);
+        return ApiResponse.success(null);
+    }
+}

+ 64 - 0
backend/src/main/java/com/oms/controller/FinanceRefundController.java

@@ -0,0 +1,64 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.FinanceRefundDTO;
+import com.oms.entity.FinanceRefund;
+import com.oms.service.FinanceRefundService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/finance/refunds")
+@RequiredArgsConstructor
+public class FinanceRefundController {
+
+    private final FinanceRefundService financeRefundService;
+
+    @GetMapping
+    public ApiResponse<List<FinanceRefund>> getRefunds(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(financeRefundService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<FinanceRefund>> getAll() {
+        return ApiResponse.success(financeRefundService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<FinanceRefundDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(financeRefundService.getDtoById(id));
+    }
+
+    @GetMapping("/refund-no/{refundNo}")
+    public ApiResponse<FinanceRefund> getByRefundNo(@PathVariable String refundNo) {
+        return ApiResponse.success(financeRefundService.getByRefundNo(refundNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody FinanceRefund refund) {
+        return ApiResponse.success(financeRefundService.save(refund));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody FinanceRefund refund) {
+        refund.setId(id);
+        financeRefundService.update(refund);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        financeRefundService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/process-refund")
+    public ApiResponse<Void> processRefund(@PathVariable Long id) {
+        financeRefundService.processRefund(id);
+        return ApiResponse.success(null);
+    }
+}

+ 95 - 0
backend/src/main/java/com/oms/controller/InventoryController.java

@@ -0,0 +1,95 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.InventoryDTO;
+import com.oms.entity.Inventory;
+import com.oms.service.InventoryService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/inventory/inventories")
+@RequiredArgsConstructor
+public class InventoryController {
+
+    private final InventoryService inventoryService;
+
+    @GetMapping
+    public ApiResponse<List<Inventory>> getInventories(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(inventoryService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Inventory>> getAll() {
+        return ApiResponse.success(inventoryService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<InventoryDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(inventoryService.getDtoById(id));
+    }
+
+    @GetMapping("/sku/{skuId}")
+    public ApiResponse<List<Inventory>> getBySkuId(@PathVariable Long skuId) {
+        return ApiResponse.success(inventoryService.getBySkuId(skuId));
+    }
+
+    @GetMapping("/sku/{skuId}/warehouse/{warehouseId}")
+    public ApiResponse<Inventory> getBySkuIdAndWarehouseId(
+            @PathVariable Long skuId,
+            @PathVariable Long warehouseId) {
+        return ApiResponse.success(inventoryService.getBySkuIdAndWarehouseId(skuId, warehouseId));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Inventory inventory) {
+        return ApiResponse.success(inventoryService.save(inventory));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Inventory inventory) {
+        inventory.setId(id);
+        inventoryService.update(inventory);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/lock")
+    public ApiResponse<Void> lockInventory(
+            @PathVariable Long id,
+            @RequestParam int qty,
+            @RequestParam String operator) {
+        inventoryService.lockInventory(id, qty, operator);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/unlock")
+    public ApiResponse<Void> unlockInventory(
+            @PathVariable Long id,
+            @RequestParam int qty,
+            @RequestParam String operator) {
+        inventoryService.unlockInventory(id, qty, operator);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/increment")
+    public ApiResponse<Void> incrementInventory(
+            @PathVariable Long id,
+            @RequestParam int qty,
+            @RequestParam String changeType,
+            @RequestParam String operator,
+            @RequestParam(required = false) Long orderId,
+            @RequestParam(required = false) String orderNo) {
+        inventoryService.incrementInventory(id, qty, changeType, operator, orderId, orderNo);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        inventoryService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 45 - 0
backend/src/main/java/com/oms/controller/InventoryLogController.java

@@ -0,0 +1,45 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.entity.InventoryLog;
+import com.oms.service.InventoryLogService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/inventory")
+@RequiredArgsConstructor
+public class InventoryLogController {
+
+    private final InventoryLogService inventoryLogService;
+
+    @GetMapping("/{inventoryId}/logs")
+    public ApiResponse<List<InventoryLog>> getByInventoryId(@PathVariable Long inventoryId) {
+        return ApiResponse.success(inventoryLogService.getAll());
+    }
+
+    @GetMapping("/log/{id}")
+    public ApiResponse<InventoryLog> getById(@PathVariable Long id) {
+        return ApiResponse.success(inventoryLogService.getById(id));
+    }
+
+    @PostMapping("/log")
+    public ApiResponse<Long> create(@RequestBody InventoryLog inventoryLog) {
+        return ApiResponse.success(inventoryLogService.save(inventoryLog));
+    }
+
+    @PutMapping("/log/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody InventoryLog inventoryLog) {
+        inventoryLog.setId(id);
+        inventoryLogService.update(inventoryLog);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/log/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        inventoryLogService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/InventoryTurnoverController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.InventoryTurnoverDTO;
+import com.oms.entity.InventoryTurnover;
+import com.oms.service.InventoryTurnoverService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/report/inventory-turnovers")
+@RequiredArgsConstructor
+public class InventoryTurnoverController {
+    private final InventoryTurnoverService inventoryTurnoverService;
+
+    @GetMapping
+    public ApiResponse<List<InventoryTurnover>> getInventoryTurnovers(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(inventoryTurnoverService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<InventoryTurnover>> getAll() {
+        return ApiResponse.success(inventoryTurnoverService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<InventoryTurnoverDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(inventoryTurnoverService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody InventoryTurnover inventoryTurnover) {
+        return ApiResponse.success(inventoryTurnoverService.save(inventoryTurnover));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody InventoryTurnover inventoryTurnover) {
+        inventoryTurnover.setId(id);
+        inventoryTurnoverService.update(inventoryTurnover);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        inventoryTurnoverService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 57 - 0
backend/src/main/java/com/oms/controller/InvoiceController.java

@@ -0,0 +1,57 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.InvoiceDTO;
+import com.oms.entity.Invoice;
+import com.oms.service.InvoiceService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/finance/invoices")
+@RequiredArgsConstructor
+public class InvoiceController {
+
+    private final InvoiceService invoiceService;
+
+    @GetMapping
+    public ApiResponse<List<Invoice>> getInvoices(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(invoiceService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Invoice>> getAll() {
+        return ApiResponse.success(invoiceService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<InvoiceDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(invoiceService.getDtoById(id));
+    }
+
+    @GetMapping("/invoice-no/{invoiceNo}")
+    public ApiResponse<Invoice> getByInvoiceNo(@PathVariable String invoiceNo) {
+        return ApiResponse.success(invoiceService.getByInvoiceNo(invoiceNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Invoice invoice) {
+        return ApiResponse.success(invoiceService.save(invoice));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Invoice invoice) {
+        invoice.setId(id);
+        invoiceService.update(invoice);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { invoiceService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/issue")
+    public ApiResponse<Void> issue(@PathVariable Long id) { invoiceService.issue(id); return ApiResponse.success(null); }
+}

+ 48 - 0
backend/src/main/java/com/oms/controller/IqcController.java

@@ -0,0 +1,48 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.IqcDTO;
+import com.oms.entity.Iqc;
+import com.oms.service.IqcService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/purchase")
+@RequiredArgsConstructor
+public class IqcController {
+
+    private final IqcService iqcService;
+
+    @GetMapping("/{arrivalId}/iqc")
+    public ApiResponse<IqcDTO> getByArrivalId(@PathVariable Long arrivalId) {
+        return ApiResponse.success(iqcService.getDtoById(arrivalId));
+    }
+
+    @GetMapping("/iqc/{id}")
+    public ApiResponse<IqcDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(iqcService.getDtoById(id));
+    }
+
+    @GetMapping("/iqc/no/{iqcNo}")
+    public ApiResponse<Iqc> getByIqcNo(@PathVariable String iqcNo) {
+        return ApiResponse.success(iqcService.getByIqcNo(iqcNo));
+    }
+
+    @PostMapping("/iqc")
+    public ApiResponse<Long> create(@RequestBody Iqc iqc) {
+        return ApiResponse.success(iqcService.save(iqc));
+    }
+
+    @PutMapping("/iqc/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Iqc iqc) {
+        iqc.setId(id);
+        iqcService.update(iqc);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/iqc/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { iqcService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/iqc/{id}/inspect")
+    public ApiResponse<Void> inspect(@PathVariable Long id, @RequestParam String result, @RequestParam String inspector) { iqcService.inspect(id, result, inspector); return ApiResponse.success(null); }
+}

+ 51 - 0
backend/src/main/java/com/oms/controller/KnowledgeBaseController.java

@@ -0,0 +1,51 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.KnowledgeBaseDTO;
+import com.oms.entity.KnowledgeBase;
+import com.oms.service.KnowledgeBaseService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/knowledge-base")
+@RequiredArgsConstructor
+public class KnowledgeBaseController {
+    private final KnowledgeBaseService knowledgeBaseService;
+
+    @GetMapping
+    public ApiResponse<List<KnowledgeBase>> getKnowledgeBases(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(knowledgeBaseService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<KnowledgeBase>> getAll() {
+        return ApiResponse.success(knowledgeBaseService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<KnowledgeBaseDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(knowledgeBaseService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody KnowledgeBase knowledgeBase) {
+        return ApiResponse.success(knowledgeBaseService.save(knowledgeBase));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody KnowledgeBase knowledgeBase) {
+        knowledgeBase.setId(id);
+        knowledgeBaseService.update(knowledgeBase);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { knowledgeBaseService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/toggle-enabled")
+    public ApiResponse<Void> toggleEnabled(@PathVariable Long id, @RequestParam boolean enabled) { knowledgeBaseService.toggleEnabled(id, enabled); return ApiResponse.success(null); }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/KnowledgeCategoryController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.KnowledgeCategoryDTO;
+import com.oms.entity.KnowledgeCategory;
+import com.oms.service.KnowledgeCategoryService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/knowledge-categories")
+@RequiredArgsConstructor
+public class KnowledgeCategoryController {
+    private final KnowledgeCategoryService knowledgeCategoryService;
+
+    @GetMapping
+    public ApiResponse<List<KnowledgeCategory>> getKnowledgeCategories(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(knowledgeCategoryService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<KnowledgeCategory>> getAll() {
+        return ApiResponse.success(knowledgeCategoryService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<KnowledgeCategoryDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(knowledgeCategoryService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody KnowledgeCategory knowledgeCategory) {
+        return ApiResponse.success(knowledgeCategoryService.save(knowledgeCategory));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody KnowledgeCategory knowledgeCategory) {
+        knowledgeCategory.setId(id);
+        knowledgeCategoryService.update(knowledgeCategory);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        knowledgeCategoryService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 53 - 0
backend/src/main/java/com/oms/controller/LogisticsProviderController.java

@@ -0,0 +1,53 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.LogisticsProviderDTO;
+import com.oms.entity.LogisticsProvider;
+import com.oms.service.LogisticsProviderService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/logistics/providers")
+@RequiredArgsConstructor
+public class LogisticsProviderController {
+
+    private final LogisticsProviderService logisticsProviderService;
+
+    @GetMapping
+    public ApiResponse<List<LogisticsProvider>> getProviders(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(logisticsProviderService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<LogisticsProvider>> getAll() {
+        return ApiResponse.success(logisticsProviderService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<LogisticsProviderDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(logisticsProviderService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody LogisticsProvider provider) {
+        return ApiResponse.success(logisticsProviderService.save(provider));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody LogisticsProvider provider) {
+        provider.setId(id);
+        logisticsProviderService.update(provider);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        logisticsProviderService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 46 - 0
backend/src/main/java/com/oms/controller/OrderItemController.java

@@ -0,0 +1,46 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.OrderItemDTO;
+import com.oms.entity.OrderItem;
+import com.oms.service.OrderItemService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/order")
+@RequiredArgsConstructor
+public class OrderItemController {
+
+    private final OrderItemService orderItemService;
+
+    @GetMapping("/{orderId}/items")
+    public ApiResponse<List<OrderItem>> getByOrderId(@PathVariable Long orderId) {
+        return ApiResponse.success(orderItemService.getByOrderId(orderId));
+    }
+
+    @GetMapping("/item/{id}")
+    public ApiResponse<OrderItemDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(orderItemService.getDtoById(id));
+    }
+
+    @PostMapping("/item")
+    public ApiResponse<Long> create(@RequestBody OrderItem orderItem) {
+        return ApiResponse.success(orderItemService.save(orderItem));
+    }
+
+    @PutMapping("/item/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody OrderItem orderItem) {
+        orderItem.setId(id);
+        orderItemService.update(orderItem);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/item/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        orderItemService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 46 - 0
backend/src/main/java/com/oms/controller/OrderOperationLogController.java

@@ -0,0 +1,46 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.OrderOperationLogDTO;
+import com.oms.entity.OrderOperationLog;
+import com.oms.service.OrderOperationLogService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/order")
+@RequiredArgsConstructor
+public class OrderOperationLogController {
+
+    private final OrderOperationLogService orderOperationLogService;
+
+    @GetMapping("/{orderId}/operation-logs")
+    public ApiResponse<List<OrderOperationLog>> getByOrderId(@PathVariable Long orderId) {
+        return ApiResponse.success(orderOperationLogService.getByOrderId(orderId));
+    }
+
+    @GetMapping("/operation-log/{id}")
+    public ApiResponse<OrderOperationLogDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(orderOperationLogService.getDtoById(id));
+    }
+
+    @PostMapping("/operation-log")
+    public ApiResponse<Long> create(@RequestBody OrderOperationLog orderOperationLog) {
+        return ApiResponse.success(orderOperationLogService.save(orderOperationLog));
+    }
+
+    @PutMapping("/operation-log/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody OrderOperationLog orderOperationLog) {
+        orderOperationLog.setId(id);
+        orderOperationLogService.update(orderOperationLog);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/operation-log/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        orderOperationLogService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 46 - 0
backend/src/main/java/com/oms/controller/OrderStatusEventController.java

@@ -0,0 +1,46 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.OrderStatusEventDTO;
+import com.oms.entity.OrderStatusEvent;
+import com.oms.service.OrderStatusEventService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/order")
+@RequiredArgsConstructor
+public class OrderStatusEventController {
+
+    private final OrderStatusEventService orderStatusEventService;
+
+    @GetMapping("/{orderId}/status-events")
+    public ApiResponse<List<OrderStatusEvent>> getByOrderId(@PathVariable Long orderId) {
+        return ApiResponse.success(orderStatusEventService.getByOrderId(orderId));
+    }
+
+    @GetMapping("/status-event/{id}")
+    public ApiResponse<OrderStatusEventDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(orderStatusEventService.getDtoById(id));
+    }
+
+    @PostMapping("/status-event")
+    public ApiResponse<Long> create(@RequestBody OrderStatusEvent orderStatusEvent) {
+        return ApiResponse.success(orderStatusEventService.save(orderStatusEvent));
+    }
+
+    @PutMapping("/status-event/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody OrderStatusEvent orderStatusEvent) {
+        orderStatusEvent.setId(id);
+        orderStatusEventService.update(orderStatusEvent);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/status-event/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        orderStatusEventService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 117 - 0
backend/src/main/java/com/oms/controller/OrdersController.java

@@ -0,0 +1,117 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.OrdersDTO;
+import com.oms.entity.Orders;
+import com.oms.service.OrdersService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/order/orders")
+@RequiredArgsConstructor
+public class OrdersController {
+
+    private final OrdersService ordersService;
+
+    @GetMapping
+    public ApiResponse<List<Orders>> getOrders(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(ordersService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Orders>> getAll() {
+        return ApiResponse.success(ordersService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<OrdersDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(ordersService.getDtoById(id));
+    }
+
+    @GetMapping("/order-no/{orderNo}")
+    public ApiResponse<Orders> getByOrderNo(@PathVariable String orderNo) {
+        return ApiResponse.success(ordersService.getByOrderNo(orderNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Orders order) {
+        return ApiResponse.success(ordersService.save(order));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Orders order) {
+        order.setId(id);
+        ordersService.update(order);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        ordersService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/confirm-payment")
+    public ApiResponse<Void> confirmPayment(@PathVariable Long id) {
+        ordersService.confirmPayment(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/allocate")
+    public ApiResponse<Void> allocateOrder(@PathVariable Long id) {
+        ordersService.allocateOrder(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/cancel")
+    public ApiResponse<Void> cancelOrder(@PathVariable Long id, @RequestParam String reason) {
+        ordersService.cancelOrder(id, reason, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/confirm-shipped")
+    public ApiResponse<Void> confirmShipped(@PathVariable Long id) {
+        ordersService.confirmShipped(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/confirm-delivered")
+    public ApiResponse<Void> confirmDelivered(@PathVariable Long id) {
+        ordersService.confirmDelivered(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/complete")
+    public ApiResponse<Void> completeOrder(@PathVariable Long id) {
+        ordersService.completeOrder(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/split")
+    public ApiResponse<Long> splitOrder(@PathVariable Long id, @RequestBody java.util.List<java.util.Map<String, Object>> splits) {
+        return ApiResponse.success(ordersService.splitOrder(id, splits, "SYSTEM"));
+    }
+
+    @PostMapping("/{sourceId}/merge/{targetId}")
+    public ApiResponse<Void> mergeOrders(@PathVariable Long sourceId, @PathVariable Long targetId) {
+        ordersService.mergeOrders(sourceId, targetId, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PutMapping("/{id}/handler")
+    public ApiResponse<Void> assignHandler(@PathVariable Long id, @RequestParam Long handlerId, @RequestParam String handlerName) {
+        ordersService.assignHandler(id, handlerId, handlerName);
+        return ApiResponse.success(null);
+    }
+
+    @PutMapping("/{id}/warehouse")
+    public ApiResponse<Void> assignWarehouse(@PathVariable Long id, @RequestParam Long warehouseId) {
+        ordersService.assignWarehouse(id, warehouseId);
+        return ApiResponse.success(null);
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/PriceWatchController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.PriceWatchDTO;
+import com.oms.entity.PriceWatch;
+import com.oms.service.PriceWatchService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/marketing/price-watches")
+@RequiredArgsConstructor
+public class PriceWatchController {
+    private final PriceWatchService priceWatchService;
+
+    @GetMapping
+    public ApiResponse<List<PriceWatch>> getPriceWatches(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(priceWatchService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<PriceWatch>> getAll() {
+        return ApiResponse.success(priceWatchService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<PriceWatchDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(priceWatchService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody PriceWatch priceWatch) {
+        return ApiResponse.success(priceWatchService.save(priceWatch));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody PriceWatch priceWatch) {
+        priceWatch.setId(id);
+        priceWatchService.update(priceWatch);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        priceWatchService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/ProductCategoryController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.entity.ProductCategory;
+import com.oms.service.ProductCategoryService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/product/categories")
+@RequiredArgsConstructor
+public class ProductCategoryController {
+
+    private final ProductCategoryService productCategoryService;
+
+    @GetMapping
+    public ApiResponse<List<ProductCategory>> getCategories(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(productCategoryService.getCategories(page, size).getRecords());
+    }
+
+    @GetMapping("/tree")
+    public ApiResponse<List<ProductCategory>> getCategoryTree() {
+        return ApiResponse.success(productCategoryService.getTree());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ProductCategory> getById(@PathVariable Long id) {
+        return ApiResponse.success(productCategoryService.getById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ProductCategory category) {
+        return ApiResponse.success(productCategoryService.save(category));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ProductCategory category) {
+        category.setId(id);
+        productCategoryService.update(category);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        productCategoryService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 69 - 0
backend/src/main/java/com/oms/controller/ProductController.java

@@ -0,0 +1,69 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.entity.Product;
+import com.oms.service.ProductService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/product/products")
+@RequiredArgsConstructor
+public class ProductController {
+
+    private final ProductService productService;
+
+    @GetMapping
+    public ApiResponse<List<Product>> getProducts(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(productService.getProducts(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Product>> getAll() {
+        return ApiResponse.success(productService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<Product> getById(@PathVariable Long id) {
+        return ApiResponse.success(productService.getById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Product product) {
+        return ApiResponse.success(productService.save(product));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Product product) {
+        product.setId(id);
+        productService.update(product);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        productService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/publish")
+    public ApiResponse<Void> publish(@PathVariable Long id) {
+        productService.publish(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/unpublish")
+    public ApiResponse<Void> unpublish(@PathVariable Long id) {
+        productService.unpublish(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/copy")
+    public ApiResponse<Long> copy(@PathVariable Long id) {
+        return ApiResponse.success(productService.copy(id));
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/ProductSkuController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.entity.ProductSku;
+import com.oms.service.ProductSkuService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/product/skus")
+@RequiredArgsConstructor
+public class ProductSkuController {
+
+    private final ProductSkuService productSkuService;
+
+    @GetMapping
+    public ApiResponse<List<ProductSku>> getSkus(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(productSkuService.getSkus(page, size).getRecords());
+    }
+
+    @GetMapping("/product/{productId}")
+    public ApiResponse<List<ProductSku>> getByProductId(@PathVariable Long productId) {
+        return ApiResponse.success(productSkuService.getByProductId(productId));
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ProductSku> getById(@PathVariable Long id) {
+        return ApiResponse.success(productSkuService.getById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ProductSku sku) {
+        return ApiResponse.success(productSkuService.save(sku));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ProductSku sku) {
+        sku.setId(id);
+        productSkuService.update(sku);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        productSkuService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 53 - 0
backend/src/main/java/com/oms/controller/PromotionController.java

@@ -0,0 +1,53 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.PromotionDTO;
+import com.oms.entity.Promotion;
+import com.oms.service.PromotionService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/marketing/promotions")
+@RequiredArgsConstructor
+public class PromotionController {
+    private final PromotionService promotionService;
+
+    @GetMapping
+    public ApiResponse<List<Promotion>> getPromotions(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(promotionService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Promotion>> getAll() {
+        return ApiResponse.success(promotionService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<PromotionDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(promotionService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Promotion promotion) {
+        return ApiResponse.success(promotionService.save(promotion));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Promotion promotion) {
+        promotion.setId(id);
+        promotionService.update(promotion);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { promotionService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/activate")
+    public ApiResponse<Void> activate(@PathVariable Long id) { promotionService.activate(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/deactivate")
+    public ApiResponse<Void> deactivate(@PathVariable Long id) { promotionService.deactivate(id); return ApiResponse.success(null); }
+}

+ 51 - 0
backend/src/main/java/com/oms/controller/PurchaseArrivalController.java

@@ -0,0 +1,51 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.PurchaseArrivalDTO;
+import com.oms.entity.PurchaseArrival;
+import com.oms.service.PurchaseArrivalService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/purchase")
+@RequiredArgsConstructor
+public class PurchaseArrivalController {
+
+    private final PurchaseArrivalService purchaseArrivalService;
+
+    @GetMapping("/{purchaseOrderId}/arrivals")
+    public ApiResponse<List<PurchaseArrival>> getByPurchaseOrderId(@PathVariable Long purchaseOrderId) {
+        return ApiResponse.success(purchaseArrivalService.getAll());
+    }
+
+    @GetMapping("/arrival/{id}")
+    public ApiResponse<PurchaseArrivalDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(purchaseArrivalService.getDtoById(id));
+    }
+
+    @GetMapping("/arrival/no/{arrivalNo}")
+    public ApiResponse<PurchaseArrival> getByArrivalNo(@PathVariable String arrivalNo) {
+        return ApiResponse.success(purchaseArrivalService.getByArrivalNo(arrivalNo));
+    }
+
+    @PostMapping("/arrival")
+    public ApiResponse<Long> create(@RequestBody PurchaseArrival purchaseArrival) {
+        return ApiResponse.success(purchaseArrivalService.save(purchaseArrival));
+    }
+
+    @PutMapping("/arrival/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody PurchaseArrival purchaseArrival) {
+        purchaseArrival.setId(id);
+        purchaseArrivalService.update(purchaseArrival);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/arrival/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        purchaseArrivalService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 46 - 0
backend/src/main/java/com/oms/controller/PurchaseArrivalItemController.java

@@ -0,0 +1,46 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.PurchaseArrivalItemDTO;
+import com.oms.entity.PurchaseArrivalItem;
+import com.oms.service.PurchaseArrivalItemService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/purchase")
+@RequiredArgsConstructor
+public class PurchaseArrivalItemController {
+
+    private final PurchaseArrivalItemService purchaseArrivalItemService;
+
+    @GetMapping("/{arrivalId}/items")
+    public ApiResponse<List<PurchaseArrivalItem>> getByArrivalId(@PathVariable Long arrivalId) {
+        return ApiResponse.success(purchaseArrivalItemService.getByArrivalId(arrivalId));
+    }
+
+    @GetMapping("/arrival-item/{id}")
+    public ApiResponse<PurchaseArrivalItemDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(purchaseArrivalItemService.getDtoById(id));
+    }
+
+    @PostMapping("/arrival-item")
+    public ApiResponse<Long> create(@RequestBody PurchaseArrivalItem purchaseArrivalItem) {
+        return ApiResponse.success(purchaseArrivalItemService.save(purchaseArrivalItem));
+    }
+
+    @PutMapping("/arrival-item/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody PurchaseArrivalItem purchaseArrivalItem) {
+        purchaseArrivalItem.setId(id);
+        purchaseArrivalItemService.update(purchaseArrivalItem);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/arrival-item/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        purchaseArrivalItemService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 64 - 0
backend/src/main/java/com/oms/controller/PurchaseOrderController.java

@@ -0,0 +1,64 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.PurchaseOrderDTO;
+import com.oms.entity.PurchaseOrder;
+import com.oms.service.PurchaseOrderService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/purchase/orders")
+@RequiredArgsConstructor
+public class PurchaseOrderController {
+
+    private final PurchaseOrderService purchaseOrderService;
+
+    @GetMapping
+    public ApiResponse<List<PurchaseOrder>> getOrders(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(purchaseOrderService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<PurchaseOrder>> getAll() {
+        return ApiResponse.success(purchaseOrderService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<PurchaseOrderDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(purchaseOrderService.getDtoById(id));
+    }
+
+    @GetMapping("/po-no/{poNo}")
+    public ApiResponse<PurchaseOrder> getByPoNo(@PathVariable String poNo) {
+        return ApiResponse.success(purchaseOrderService.getByPoNo(poNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody PurchaseOrder purchaseOrder) {
+        return ApiResponse.success(purchaseOrderService.save(purchaseOrder));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody PurchaseOrder purchaseOrder) {
+        purchaseOrder.setId(id);
+        purchaseOrderService.update(purchaseOrder);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        purchaseOrderService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/submit")
+    public ApiResponse<Void> submit(@PathVariable Long id) {
+        purchaseOrderService.submit(id);
+        return ApiResponse.success(null);
+    }
+}

+ 46 - 0
backend/src/main/java/com/oms/controller/PurchaseOrderItemController.java

@@ -0,0 +1,46 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.PurchaseOrderItemDTO;
+import com.oms.entity.PurchaseOrderItem;
+import com.oms.service.PurchaseOrderItemService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/purchase")
+@RequiredArgsConstructor
+public class PurchaseOrderItemController {
+
+    private final PurchaseOrderItemService purchaseOrderItemService;
+
+    @GetMapping("/{purchaseOrderId}/items")
+    public ApiResponse<List<PurchaseOrderItem>> getByPurchaseOrderId(@PathVariable Long purchaseOrderId) {
+        return ApiResponse.success(purchaseOrderItemService.getByPurchaseOrderId(purchaseOrderId));
+    }
+
+    @GetMapping("/item/{id}")
+    public ApiResponse<PurchaseOrderItemDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(purchaseOrderItemService.getDtoById(id));
+    }
+
+    @PostMapping("/item")
+    public ApiResponse<Long> create(@RequestBody PurchaseOrderItem purchaseOrderItem) {
+        return ApiResponse.success(purchaseOrderItemService.save(purchaseOrderItem));
+    }
+
+    @PutMapping("/item/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody PurchaseOrderItem purchaseOrderItem) {
+        purchaseOrderItem.setId(id);
+        purchaseOrderItemService.update(purchaseOrderItem);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/item/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        purchaseOrderItemService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/PurchaseRequestController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.PurchaseRequestDTO;
+import com.oms.entity.PurchaseRequest;
+import com.oms.service.PurchaseRequestService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/purchase/requests")
+@RequiredArgsConstructor
+public class PurchaseRequestController {
+
+    private final PurchaseRequestService purchaseRequestService;
+
+    @GetMapping
+    public ApiResponse<List<PurchaseRequest>> getAll() {
+        return ApiResponse.success(purchaseRequestService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<PurchaseRequestDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(purchaseRequestService.getDtoById(id));
+    }
+
+    @GetMapping("/request-no/{requestNo}")
+    public ApiResponse<PurchaseRequest> getByRequestNo(@PathVariable String requestNo) {
+        return ApiResponse.success(purchaseRequestService.getByRequestNo(requestNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody PurchaseRequest purchaseRequest) {
+        return ApiResponse.success(purchaseRequestService.save(purchaseRequest));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody PurchaseRequest purchaseRequest) {
+        purchaseRequest.setId(id);
+        purchaseRequestService.update(purchaseRequest);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { purchaseRequestService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/approve")
+    public ApiResponse<Void> approve(@PathVariable Long id) { purchaseRequestService.approve(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/reject")
+    public ApiResponse<Void> reject(@PathVariable Long id, @RequestParam String reason) { purchaseRequestService.reject(id, reason); return ApiResponse.success(null); }
+}

+ 51 - 0
backend/src/main/java/com/oms/controller/ReplenishmentPlanController.java

@@ -0,0 +1,51 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ReplenishmentPlanDTO;
+import com.oms.entity.ReplenishmentPlan;
+import com.oms.service.ReplenishmentPlanService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/inventory/replenishment-plans")
+@RequiredArgsConstructor
+public class ReplenishmentPlanController {
+
+    private final ReplenishmentPlanService replenishmentPlanService;
+
+    @GetMapping
+    public ApiResponse<List<ReplenishmentPlan>> getAll() {
+        return ApiResponse.success(replenishmentPlanService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ReplenishmentPlanDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(replenishmentPlanService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ReplenishmentPlan replenishmentPlan) {
+        return ApiResponse.success(replenishmentPlanService.save(replenishmentPlan));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ReplenishmentPlan replenishmentPlan) {
+        replenishmentPlan.setId(id);
+        replenishmentPlanService.update(replenishmentPlan);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        replenishmentPlanService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/generate-suggestions")
+    public ApiResponse<Integer> generateSuggestions() {
+        return ApiResponse.success(replenishmentPlanService.generateSuggestions());
+    }
+}

+ 59 - 0
backend/src/main/java/com/oms/controller/ReturnPackageController.java

@@ -0,0 +1,59 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ReturnPackageDTO;
+import com.oms.entity.ReturnPackage;
+import com.oms.service.ReturnPackageService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/warehouse/returns")
+@RequiredArgsConstructor
+public class ReturnPackageController {
+
+    private final ReturnPackageService returnPackageService;
+
+    @GetMapping
+    public ApiResponse<List<ReturnPackage>> getReturns(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(returnPackageService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<ReturnPackage>> getAll() {
+        return ApiResponse.success(returnPackageService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ReturnPackageDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(returnPackageService.getDtoById(id));
+    }
+
+    @GetMapping("/return-no/{returnNo}")
+    public ApiResponse<ReturnPackage> getByReturnNo(@PathVariable String returnNo) {
+        return ApiResponse.success(returnPackageService.getByReturnNo(returnNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ReturnPackage returnPackage) {
+        return ApiResponse.success(returnPackageService.save(returnPackage));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ReturnPackage returnPackage) {
+        returnPackage.setId(id);
+        returnPackageService.update(returnPackage);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { returnPackageService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/receive")
+    public ApiResponse<Void> receive(@PathVariable Long id) { returnPackageService.receive(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/confirm-inspection")
+    public ApiResponse<Void> confirmInspection(@PathVariable Long id, @RequestParam String result) { returnPackageService.confirmInspection(id, result); return ApiResponse.success(null); }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/ServicePerformanceController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ServicePerformanceDTO;
+import com.oms.entity.ServicePerformance;
+import com.oms.service.ServicePerformanceService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/ai/service-performances")
+@RequiredArgsConstructor
+public class ServicePerformanceController {
+    private final ServicePerformanceService servicePerformanceService;
+
+    @GetMapping
+    public ApiResponse<List<ServicePerformance>> getServicePerformances(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(servicePerformanceService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<ServicePerformance>> getAll() {
+        return ApiResponse.success(servicePerformanceService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ServicePerformanceDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(servicePerformanceService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ServicePerformance servicePerformance) {
+        return ApiResponse.success(servicePerformanceService.save(servicePerformance));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ServicePerformance servicePerformance) {
+        servicePerformance.setId(id);
+        servicePerformanceService.update(servicePerformance);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        servicePerformanceService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 76 - 0
backend/src/main/java/com/oms/controller/ShippingOrderController.java

@@ -0,0 +1,76 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ShippingOrderDTO;
+import com.oms.entity.ShippingOrder;
+import com.oms.service.ShippingOrderService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/order/shipping-orders")
+@RequiredArgsConstructor
+public class ShippingOrderController {
+
+    private final ShippingOrderService shippingOrderService;
+
+    @GetMapping
+    public ApiResponse<List<ShippingOrder>> getShippingOrders(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(shippingOrderService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<ShippingOrder>> getAll() {
+        return ApiResponse.success(shippingOrderService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ShippingOrderDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(shippingOrderService.getDtoById(id));
+    }
+
+    @GetMapping("/shipment-no/{shipmentNo}")
+    public ApiResponse<ShippingOrder> getByShipmentNo(@PathVariable String shipmentNo) {
+        return ApiResponse.success(shippingOrderService.getByShipmentNo(shipmentNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ShippingOrder shippingOrder) {
+        return ApiResponse.success(shippingOrderService.save(shippingOrder));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ShippingOrder shippingOrder) {
+        shippingOrder.setId(id);
+        shippingOrderService.update(shippingOrder);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        shippingOrderService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/submit-tracking")
+    public ApiResponse<Void> submitTracking(@PathVariable Long id, @RequestParam String carrier, @RequestParam String trackingNo) {
+        shippingOrderService.submitTracking(id, carrier, trackingNo, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/confirm-shipped")
+    public ApiResponse<Void> confirmShipped(@PathVariable Long id) {
+        shippingOrderService.confirmShipped(id, "SYSTEM");
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/retry-return")
+    public ApiResponse<Void> retryReturn(@PathVariable Long id) {
+        shippingOrderService.retryReturn(id);
+        return ApiResponse.success(null);
+    }
+}

+ 58 - 0
backend/src/main/java/com/oms/controller/ShippingTemplateController.java

@@ -0,0 +1,58 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.ShippingTemplateDTO;
+import com.oms.entity.ShippingTemplate;
+import com.oms.service.ShippingTemplateService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/logistics/shipping-templates")
+@RequiredArgsConstructor
+public class ShippingTemplateController {
+
+    private final ShippingTemplateService shippingTemplateService;
+
+    @GetMapping
+    public ApiResponse<List<ShippingTemplate>> getTemplates(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(shippingTemplateService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<ShippingTemplate>> getAll() {
+        return ApiResponse.success(shippingTemplateService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<ShippingTemplateDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(shippingTemplateService.getDtoById(id));
+    }
+
+    @GetMapping("/carrier/{carrierId}")
+    public ApiResponse<ShippingTemplate> getByCarrierId(@PathVariable Long carrierId) {
+        return ApiResponse.success(shippingTemplateService.getByCarrierId(carrierId));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody ShippingTemplate template) {
+        return ApiResponse.success(shippingTemplateService.save(template));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody ShippingTemplate template) {
+        template.setId(id);
+        shippingTemplateService.update(template);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        shippingTemplateService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 58 - 0
backend/src/main/java/com/oms/controller/SupplierController.java

@@ -0,0 +1,58 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.SupplierDTO;
+import com.oms.entity.Supplier;
+import com.oms.service.SupplierService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/supplier/suppliers")
+@RequiredArgsConstructor
+public class SupplierController {
+
+    private final SupplierService supplierService;
+
+    @GetMapping
+    public ApiResponse<List<Supplier>> getSuppliers(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(supplierService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Supplier>> getAll() {
+        return ApiResponse.success(supplierService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<SupplierDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(supplierService.getDtoById(id));
+    }
+
+    @GetMapping("/name/{name}")
+    public ApiResponse<Supplier> getByName(@PathVariable String name) {
+        return ApiResponse.success(supplierService.getByName(name));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Supplier supplier) {
+        return ApiResponse.success(supplierService.save(supplier));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Supplier supplier) {
+        supplier.setId(id);
+        supplierService.update(supplier);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        supplierService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/SupplierPerformanceController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.SupplierPerformanceDTO;
+import com.oms.entity.SupplierPerformance;
+import com.oms.service.SupplierPerformanceService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/marketing/supplier-performances")
+@RequiredArgsConstructor
+public class SupplierPerformanceController {
+    private final SupplierPerformanceService supplierPerformanceService;
+
+    @GetMapping
+    public ApiResponse<List<SupplierPerformance>> getSupplierPerformances(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(supplierPerformanceService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<SupplierPerformance>> getAll() {
+        return ApiResponse.success(supplierPerformanceService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<SupplierPerformanceDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(supplierPerformanceService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody SupplierPerformance supplierPerformance) {
+        return ApiResponse.success(supplierPerformanceService.save(supplierPerformance));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody SupplierPerformance supplierPerformance) {
+        supplierPerformance.setId(id);
+        supplierPerformanceService.update(supplierPerformance);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        supplierPerformanceService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 69 - 0
backend/src/main/java/com/oms/controller/SupplierSettlementController.java

@@ -0,0 +1,69 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.SupplierSettlementDTO;
+import com.oms.entity.SupplierSettlement;
+import com.oms.service.SupplierSettlementService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/finance/settlements")
+@RequiredArgsConstructor
+public class SupplierSettlementController {
+
+    private final SupplierSettlementService supplierSettlementService;
+
+    @GetMapping
+    public ApiResponse<List<SupplierSettlement>> getSettlements(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(supplierSettlementService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<SupplierSettlement>> getAll() {
+        return ApiResponse.success(supplierSettlementService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<SupplierSettlementDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(supplierSettlementService.getDtoById(id));
+    }
+
+    @GetMapping("/settlement-no/{settlementNo}")
+    public ApiResponse<SupplierSettlement> getBySettlementNo(@PathVariable String settlementNo) {
+        return ApiResponse.success(supplierSettlementService.getBySettlementNo(settlementNo));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody SupplierSettlement settlement) {
+        return ApiResponse.success(supplierSettlementService.save(settlement));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody SupplierSettlement settlement) {
+        settlement.setId(id);
+        supplierSettlementService.update(settlement);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        supplierSettlementService.delete(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/{id}/settle")
+    public ApiResponse<Void> settle(@PathVariable Long id) {
+        supplierSettlementService.settle(id);
+        return ApiResponse.success(null);
+    }
+
+    @PostMapping("/generate")
+    public ApiResponse<Long> generateSettlement(@RequestParam Long supplierId, @RequestParam String period) {
+        return ApiResponse.success(supplierSettlementService.generateSettlement(supplierId, period));
+    }
+}

+ 51 - 0
backend/src/main/java/com/oms/controller/SupplyCapabilityController.java

@@ -0,0 +1,51 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.SupplyCapabilityDTO;
+import com.oms.entity.SupplyCapability;
+import com.oms.service.SupplyCapabilityService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/supplier")
+@RequiredArgsConstructor
+public class SupplyCapabilityController {
+
+    private final SupplyCapabilityService supplyCapabilityService;
+
+    @GetMapping("/{supplierId}/capabilities")
+    public ApiResponse<List<SupplyCapability>> getBySupplierId(@PathVariable Long supplierId) {
+        return ApiResponse.success(supplyCapabilityService.getBySupplierId(supplierId));
+    }
+
+    @GetMapping("/sku/{skuId}/capabilities")
+    public ApiResponse<List<SupplyCapability>> getBySkuId(@PathVariable Long skuId) {
+        return ApiResponse.success(supplyCapabilityService.getBySkuId(skuId));
+    }
+
+    @GetMapping("/capability/{id}")
+    public ApiResponse<SupplyCapabilityDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(supplyCapabilityService.getDtoById(id));
+    }
+
+    @PostMapping("/capability")
+    public ApiResponse<Long> create(@RequestBody SupplyCapability supplyCapability) {
+        return ApiResponse.success(supplyCapabilityService.save(supplyCapability));
+    }
+
+    @PutMapping("/capability/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody SupplyCapability supplyCapability) {
+        supplyCapability.setId(id);
+        supplyCapabilityService.update(supplyCapability);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/capability/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        supplyCapabilityService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 55 - 0
backend/src/main/java/com/oms/controller/SysApprovalFlowController.java

@@ -0,0 +1,55 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.SysApprovalFlowDTO;
+import com.oms.entity.SysApprovalFlow;
+import com.oms.service.SysApprovalFlowService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/system/approval-flows")
+@RequiredArgsConstructor
+public class SysApprovalFlowController {
+    private final SysApprovalFlowService sysApprovalFlowService;
+
+    @GetMapping
+    public ApiResponse<List<SysApprovalFlow>> getSysApprovalFlows(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(sysApprovalFlowService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<SysApprovalFlow>> getAll() {
+        return ApiResponse.success(sysApprovalFlowService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<SysApprovalFlowDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(sysApprovalFlowService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody SysApprovalFlow sysApprovalFlow) {
+        return ApiResponse.success(sysApprovalFlowService.save(sysApprovalFlow));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody SysApprovalFlow sysApprovalFlow) {
+        sysApprovalFlow.setId(id);
+        sysApprovalFlowService.update(sysApprovalFlow);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) { sysApprovalFlowService.delete(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/submit")
+    public ApiResponse<Void> submit(@PathVariable Long id) { sysApprovalFlowService.submit(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/approve")
+    public ApiResponse<Void> approve(@PathVariable Long id) { sysApprovalFlowService.approve(id); return ApiResponse.success(null); }
+    @PostMapping("/{id}/reject")
+    public ApiResponse<Void> reject(@PathVariable Long id) { sysApprovalFlowService.reject(id); return ApiResponse.success(null); }
+}

+ 52 - 0
backend/src/main/java/com/oms/controller/SysMessageTemplateController.java

@@ -0,0 +1,52 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.SysMessageTemplateDTO;
+import com.oms.entity.SysMessageTemplate;
+import com.oms.service.SysMessageTemplateService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/system/message-templates")
+@RequiredArgsConstructor
+public class SysMessageTemplateController {
+    private final SysMessageTemplateService sysMessageTemplateService;
+
+    @GetMapping
+    public ApiResponse<List<SysMessageTemplate>> getSysMessageTemplates(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(sysMessageTemplateService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<SysMessageTemplate>> getAll() {
+        return ApiResponse.success(sysMessageTemplateService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<SysMessageTemplateDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(sysMessageTemplateService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody SysMessageTemplate sysMessageTemplate) {
+        return ApiResponse.success(sysMessageTemplateService.save(sysMessageTemplate));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody SysMessageTemplate sysMessageTemplate) {
+        sysMessageTemplate.setId(id);
+        sysMessageTemplateService.update(sysMessageTemplate);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        sysMessageTemplateService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 53 - 0
backend/src/main/java/com/oms/controller/WarehouseController.java

@@ -0,0 +1,53 @@
+package com.oms.controller;
+
+import com.oms.common.ApiResponse;
+import com.oms.dto.WarehouseDTO;
+import com.oms.entity.Warehouse;
+import com.oms.service.WarehouseService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/warehouse/warehouses")
+@RequiredArgsConstructor
+public class WarehouseController {
+
+    private final WarehouseService warehouseService;
+
+    @GetMapping
+    public ApiResponse<List<Warehouse>> getWarehouses(
+            @RequestParam(defaultValue = "1") int page,
+            @RequestParam(defaultValue = "20") int size) {
+        return ApiResponse.success(warehouseService.getPage(page, size).getRecords());
+    }
+
+    @GetMapping("/all")
+    public ApiResponse<List<Warehouse>> getAll() {
+        return ApiResponse.success(warehouseService.getAll());
+    }
+
+    @GetMapping("/{id}")
+    public ApiResponse<WarehouseDTO> getById(@PathVariable Long id) {
+        return ApiResponse.success(warehouseService.getDtoById(id));
+    }
+
+    @PostMapping
+    public ApiResponse<Long> create(@RequestBody Warehouse warehouse) {
+        return ApiResponse.success(warehouseService.save(warehouse));
+    }
+
+    @PutMapping("/{id}")
+    public ApiResponse<Void> update(@PathVariable Long id, @RequestBody Warehouse warehouse) {
+        warehouse.setId(id);
+        warehouseService.update(warehouse);
+        return ApiResponse.success(null);
+    }
+
+    @DeleteMapping("/{id}")
+    public ApiResponse<Void> delete(@PathVariable Long id) {
+        warehouseService.delete(id);
+        return ApiResponse.success(null);
+    }
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/AfterSaleConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.AfterSaleDTO;
+import com.oms.entity.AfterSale;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface AfterSaleConverter extends BaseConverter<AfterSale, AfterSaleDTO> {
+    @Mapping(target = "id", ignore = true)
+    AfterSale toEntity(AfterSaleDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/AiChannelConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.AiChannelDTO;
+import com.oms.entity.AiChannel;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface AiChannelConverter extends BaseConverter<AiChannel, AiChannelDTO> {
+    @Mapping(target = "id", ignore = true)
+    AiChannel toEntity(AiChannelDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/AutoReplyRuleConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.AutoReplyRuleDTO;
+import com.oms.entity.AutoReplyRule;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface AutoReplyRuleConverter extends BaseConverter<AutoReplyRule, AutoReplyRuleDTO> {
+    @Mapping(target = "id", ignore = true)
+    AutoReplyRule toEntity(AutoReplyRuleDTO dto);
+}

+ 11 - 0
backend/src/main/java/com/oms/converter/BaseConverter.java

@@ -0,0 +1,11 @@
+package com.oms.converter;
+
+import java.util.List;
+import java.util.Collections;
+
+public interface BaseConverter<E, D> {
+    D toDto(E entity);
+    E toEntity(D dto);
+    List<D> toDtoList(List<E> entities);
+    List<E> toEntityList(List<D> dtos);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/ChannelConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.ChannelDTO;
+import com.oms.entity.Channel;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface ChannelConverter extends BaseConverter<Channel, ChannelDTO> {
+    @Mapping(target = "id", ignore = true)
+    Channel toEntity(ChannelDTO dto);
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/ChannelMappingConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.ChannelMappingDTO;
+import com.oms.entity.ChannelMapping;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface ChannelMappingConverter extends BaseConverter<ChannelMapping, ChannelMappingDTO> {
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/ChatMessageConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.ChatMessageDTO;
+import com.oms.entity.ChatMessage;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface ChatMessageConverter extends BaseConverter<ChatMessage, ChatMessageDTO> {
+    @Mapping(target = "id", ignore = true)
+    ChatMessage toEntity(ChatMessageDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/ChatSessionConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.ChatSessionDTO;
+import com.oms.entity.ChatSession;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface ChatSessionConverter extends BaseConverter<ChatSession, ChatSessionDTO> {
+    @Mapping(target = "id", ignore = true)
+    ChatSession toEntity(ChatSessionDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/CouponConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.CouponDTO;
+import com.oms.entity.Coupon;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface CouponConverter extends BaseConverter<Coupon, CouponDTO> {
+    @Mapping(target = "id", ignore = true)
+    Coupon toEntity(CouponDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/FinancePaymentConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.FinancePaymentDTO;
+import com.oms.entity.FinancePayment;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface FinancePaymentConverter extends BaseConverter<FinancePayment, FinancePaymentDTO> {
+    @Mapping(target = "id", ignore = true)
+    FinancePayment toEntity(FinancePaymentDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/FinanceRefundConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.FinanceRefundDTO;
+import com.oms.entity.FinanceRefund;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface FinanceRefundConverter extends BaseConverter<FinanceRefund, FinanceRefundDTO> {
+    @Mapping(target = "id", ignore = true)
+    FinanceRefund toEntity(FinanceRefundDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/InventoryConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.InventoryDTO;
+import com.oms.entity.Inventory;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface InventoryConverter extends BaseConverter<Inventory, InventoryDTO> {
+    @Mapping(target = "id", ignore = true)
+    Inventory toEntity(InventoryDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/InventoryTurnoverConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.InventoryTurnoverDTO;
+import com.oms.entity.InventoryTurnover;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface InventoryTurnoverConverter extends BaseConverter<InventoryTurnover, InventoryTurnoverDTO> {
+    @Mapping(target = "id", ignore = true)
+    InventoryTurnover toEntity(InventoryTurnoverDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/InvoiceConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.InvoiceDTO;
+import com.oms.entity.Invoice;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface InvoiceConverter extends BaseConverter<Invoice, InvoiceDTO> {
+    @Mapping(target = "id", ignore = true)
+    Invoice toEntity(InvoiceDTO dto);
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/IqcConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.IqcDTO;
+import com.oms.entity.Iqc;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface IqcConverter extends BaseConverter<Iqc, IqcDTO> {
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/KnowledgeBaseConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.KnowledgeBaseDTO;
+import com.oms.entity.KnowledgeBase;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface KnowledgeBaseConverter extends BaseConverter<KnowledgeBase, KnowledgeBaseDTO> {
+    @Mapping(target = "id", ignore = true)
+    KnowledgeBase toEntity(KnowledgeBaseDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/KnowledgeCategoryConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.KnowledgeCategoryDTO;
+import com.oms.entity.KnowledgeCategory;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface KnowledgeCategoryConverter extends BaseConverter<KnowledgeCategory, KnowledgeCategoryDTO> {
+    @Mapping(target = "id", ignore = true)
+    KnowledgeCategory toEntity(KnowledgeCategoryDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/LogisticsProviderConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.LogisticsProviderDTO;
+import com.oms.entity.LogisticsProvider;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface LogisticsProviderConverter extends BaseConverter<LogisticsProvider, LogisticsProviderDTO> {
+    @Mapping(target = "id", ignore = true)
+    LogisticsProvider toEntity(LogisticsProviderDTO dto);
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/OrderItemConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.OrderItemDTO;
+import com.oms.entity.OrderItem;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface OrderItemConverter extends BaseConverter<OrderItem, OrderItemDTO> {
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/OrderOperationLogConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.OrderOperationLogDTO;
+import com.oms.entity.OrderOperationLog;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface OrderOperationLogConverter extends BaseConverter<OrderOperationLog, OrderOperationLogDTO> {
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/OrderStatusEventConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.OrderStatusEventDTO;
+import com.oms.entity.OrderStatusEvent;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface OrderStatusEventConverter extends BaseConverter<OrderStatusEvent, OrderStatusEventDTO> {
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/OrdersConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.OrdersDTO;
+import com.oms.entity.Orders;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface OrdersConverter extends BaseConverter<Orders, OrdersDTO> {
+    @Mapping(target = "id", ignore = true)
+    Orders toEntity(OrdersDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/PriceWatchConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.PriceWatchDTO;
+import com.oms.entity.PriceWatch;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface PriceWatchConverter extends BaseConverter<PriceWatch, PriceWatchDTO> {
+    @Mapping(target = "id", ignore = true)
+    PriceWatch toEntity(PriceWatchDTO dto);
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/ProductCategoryConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.ProductCategoryDTO;
+import com.oms.entity.ProductCategory;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface ProductCategoryConverter extends BaseConverter<ProductCategory, ProductCategoryDTO> {
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/ProductConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.ProductDTO;
+import com.oms.entity.Product;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface ProductConverter extends BaseConverter<Product, ProductDTO> {
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/ProductSkuConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.ProductSkuDTO;
+import com.oms.entity.ProductSku;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface ProductSkuConverter extends BaseConverter<ProductSku, ProductSkuDTO> {
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/PromotionConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.PromotionDTO;
+import com.oms.entity.Promotion;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface PromotionConverter extends BaseConverter<Promotion, PromotionDTO> {
+    @Mapping(target = "id", ignore = true)
+    Promotion toEntity(PromotionDTO dto);
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/PurchaseArrivalConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.PurchaseArrivalDTO;
+import com.oms.entity.PurchaseArrival;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface PurchaseArrivalConverter extends BaseConverter<PurchaseArrival, PurchaseArrivalDTO> {
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/PurchaseArrivalItemConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.PurchaseArrivalItemDTO;
+import com.oms.entity.PurchaseArrivalItem;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface PurchaseArrivalItemConverter extends BaseConverter<PurchaseArrivalItem, PurchaseArrivalItemDTO> {
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/PurchaseOrderConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.PurchaseOrderDTO;
+import com.oms.entity.PurchaseOrder;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface PurchaseOrderConverter extends BaseConverter<PurchaseOrder, PurchaseOrderDTO> {
+    @Mapping(target = "id", ignore = true)
+    PurchaseOrder toEntity(PurchaseOrderDTO dto);
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/PurchaseOrderItemConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.PurchaseOrderItemDTO;
+import com.oms.entity.PurchaseOrderItem;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface PurchaseOrderItemConverter extends BaseConverter<PurchaseOrderItem, PurchaseOrderItemDTO> {
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/PurchaseRequestConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.PurchaseRequestDTO;
+import com.oms.entity.PurchaseRequest;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface PurchaseRequestConverter extends BaseConverter<PurchaseRequest, PurchaseRequestDTO> {
+}

+ 9 - 0
backend/src/main/java/com/oms/converter/ReplenishmentPlanConverter.java

@@ -0,0 +1,9 @@
+package com.oms.converter;
+
+import com.oms.dto.ReplenishmentPlanDTO;
+import com.oms.entity.ReplenishmentPlan;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface ReplenishmentPlanConverter extends BaseConverter<ReplenishmentPlan, ReplenishmentPlanDTO> {
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/ReturnPackageConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.ReturnPackageDTO;
+import com.oms.entity.ReturnPackage;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface ReturnPackageConverter extends BaseConverter<ReturnPackage, ReturnPackageDTO> {
+    @Mapping(target = "id", ignore = true)
+    ReturnPackage toEntity(ReturnPackageDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/ServicePerformanceConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.ServicePerformanceDTO;
+import com.oms.entity.ServicePerformance;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface ServicePerformanceConverter extends BaseConverter<ServicePerformance, ServicePerformanceDTO> {
+    @Mapping(target = "id", ignore = true)
+    ServicePerformance toEntity(ServicePerformanceDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/ShippingOrderConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.ShippingOrderDTO;
+import com.oms.entity.ShippingOrder;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface ShippingOrderConverter extends BaseConverter<ShippingOrder, ShippingOrderDTO> {
+    @Mapping(target = "id", ignore = true)
+    ShippingOrder toEntity(ShippingOrderDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/ShippingTemplateConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.ShippingTemplateDTO;
+import com.oms.entity.ShippingTemplate;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface ShippingTemplateConverter extends BaseConverter<ShippingTemplate, ShippingTemplateDTO> {
+    @Mapping(target = "id", ignore = true)
+    ShippingTemplate toEntity(ShippingTemplateDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/SupplierConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.SupplierDTO;
+import com.oms.entity.Supplier;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface SupplierConverter extends BaseConverter<Supplier, SupplierDTO> {
+    @Mapping(target = "id", ignore = true)
+    Supplier toEntity(SupplierDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/SupplierPerformanceConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.SupplierPerformanceDTO;
+import com.oms.entity.SupplierPerformance;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface SupplierPerformanceConverter extends BaseConverter<SupplierPerformance, SupplierPerformanceDTO> {
+    @Mapping(target = "id", ignore = true)
+    SupplierPerformance toEntity(SupplierPerformanceDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/SupplierSettlementConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.SupplierSettlementDTO;
+import com.oms.entity.SupplierSettlement;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface SupplierSettlementConverter extends BaseConverter<SupplierSettlement, SupplierSettlementDTO> {
+    @Mapping(target = "id", ignore = true)
+    SupplierSettlement toEntity(SupplierSettlementDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/SupplyCapabilityConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.SupplyCapabilityDTO;
+import com.oms.entity.SupplyCapability;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface SupplyCapabilityConverter extends BaseConverter<SupplyCapability, SupplyCapabilityDTO> {
+    @Mapping(target = "id", ignore = true)
+    SupplyCapability toEntity(SupplyCapabilityDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/SysApprovalFlowConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.SysApprovalFlowDTO;
+import com.oms.entity.SysApprovalFlow;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface SysApprovalFlowConverter extends BaseConverter<SysApprovalFlow, SysApprovalFlowDTO> {
+    @Mapping(target = "id", ignore = true)
+    SysApprovalFlow toEntity(SysApprovalFlowDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/SysMessageTemplateConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.SysMessageTemplateDTO;
+import com.oms.entity.SysMessageTemplate;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface SysMessageTemplateConverter extends BaseConverter<SysMessageTemplate, SysMessageTemplateDTO> {
+    @Mapping(target = "id", ignore = true)
+    SysMessageTemplate toEntity(SysMessageTemplateDTO dto);
+}

+ 12 - 0
backend/src/main/java/com/oms/converter/WarehouseConverter.java

@@ -0,0 +1,12 @@
+package com.oms.converter;
+
+import com.oms.dto.WarehouseDTO;
+import com.oms.entity.Warehouse;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface WarehouseConverter extends BaseConverter<Warehouse, WarehouseDTO> {
+    @Mapping(target = "id", ignore = true)
+    Warehouse toEntity(WarehouseDTO dto);
+}

+ 71 - 0
backend/src/main/java/com/oms/dto/AfterSaleDTO.java

@@ -0,0 +1,71 @@
+package com.oms.dto;
+
+import lombok.Data;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class AfterSaleDTO {
+    private Long id;
+    private String afterSaleNo;
+    private Long orderId;
+    private String orderNo;
+    private String buyerId;
+    private String buyer;
+    private String type;
+    private BigDecimal amount;
+    private String auditStatus;
+    private String refundStatus;
+    private String reason;
+    private String responsibility;
+    private BigDecimal refundAmount;
+    private String refundMethod;
+    private String returnTrackingNo;
+    private String returnCarrier;
+    private Long resendWarehouseId;
+    private Long resendSkuId;
+    private Long resendOrderId;
+    private String auditRemark;
+    private String images;
+    private Long handlerId;
+    private String handlerName;
+    private String tenantId;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+    private String createdBy;
+    private String updatedBy;
+
+    public static AfterSaleDTO fromEntity(com.oms.entity.AfterSale e) {
+        if (e == null) return null;
+        AfterSaleDTO dto = new AfterSaleDTO();
+        dto.setId(e.getId());
+        dto.setAfterSaleNo(e.getAfterSaleNo());
+        dto.setOrderId(e.getOrderId());
+        dto.setOrderNo(e.getOrderNo());
+        dto.setBuyerId(e.getBuyerId());
+        dto.setBuyer(e.getBuyer());
+        dto.setType(e.getType());
+        dto.setAmount(e.getAmount());
+        dto.setAuditStatus(e.getAuditStatus());
+        dto.setRefundStatus(e.getRefundStatus());
+        dto.setReason(e.getReason());
+        dto.setResponsibility(e.getResponsibility());
+        dto.setRefundAmount(e.getRefundAmount());
+        dto.setRefundMethod(e.getRefundMethod());
+        dto.setReturnTrackingNo(e.getReturnTrackingNo());
+        dto.setReturnCarrier(e.getReturnCarrier());
+        dto.setResendWarehouseId(e.getResendWarehouseId());
+        dto.setResendSkuId(e.getResendSkuId());
+        dto.setResendOrderId(e.getResendOrderId());
+        dto.setAuditRemark(e.getAuditRemark());
+        dto.setImages(e.getImages());
+        dto.setHandlerId(e.getHandlerId());
+        dto.setHandlerName(e.getHandlerName());
+        dto.setTenantId(e.getTenantId());
+        dto.setCreatedAt(e.getCreatedAt());
+        dto.setUpdatedAt(e.getUpdatedAt());
+        dto.setCreatedBy(e.getCreatedBy());
+        dto.setUpdatedBy(e.getUpdatedBy());
+        return dto;
+    }
+}

+ 23 - 0
backend/src/main/java/com/oms/dto/AiChannelDTO.java

@@ -0,0 +1,23 @@
+package com.oms.dto;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class AiChannelDTO {
+    private Long id;
+    private String name;
+    private String code;
+    private String icon;
+    private Integer enabled;
+    private String robotName;
+    private Integer robotEnabled;
+    private Integer autoTransferThreshold;
+    private Integer priority;
+    private String welcomeMessage;
+    private String tenantId;
+    private LocalDateTime createdAt;
+    private LocalDateTime updatedAt;
+    private String createdBy;
+    private String updatedBy;
+}

Деякі файли не було показано, через те що забагато файлів було змінено